
commit
d7420944ba
202 changed files with 41300 additions and 0 deletions
@ -0,0 +1,131 @@ |
|||
# ---> Node |
|||
# Logs |
|||
logs |
|||
*.log |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
lerna-debug.log* |
|||
.pnpm-debug.log* |
|||
|
|||
# Diagnostic reports (https://nodejs.org/api/report.html) |
|||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
|||
|
|||
# Runtime data |
|||
pids |
|||
*.pid |
|||
*.seed |
|||
*.pid.lock |
|||
|
|||
# Directory for instrumented libs generated by jscoverage/JSCover |
|||
lib-cov |
|||
|
|||
# Coverage directory used by tools like istanbul |
|||
coverage |
|||
*.lcov |
|||
|
|||
# nyc test coverage |
|||
.nyc_output |
|||
|
|||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
|||
.grunt |
|||
|
|||
# Bower dependency directory (https://bower.io/) |
|||
bower_components |
|||
|
|||
# node-waf configuration |
|||
.lock-wscript |
|||
|
|||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
|||
build/Release |
|||
|
|||
# Dependency directories |
|||
node_modules/ |
|||
jspm_packages/ |
|||
|
|||
# Snowpack dependency directory (https://snowpack.dev/) |
|||
web_modules/ |
|||
|
|||
# TypeScript cache |
|||
*.tsbuildinfo |
|||
|
|||
# Optional npm cache directory |
|||
.npm |
|||
|
|||
# Optional eslint cache |
|||
.eslintcache |
|||
|
|||
# Microbundle cache |
|||
.rpt2_cache/ |
|||
.rts2_cache_cjs/ |
|||
.rts2_cache_es/ |
|||
.rts2_cache_umd/ |
|||
|
|||
# Optional REPL history |
|||
.node_repl_history |
|||
|
|||
# Output of 'npm pack' |
|||
*.tgz |
|||
|
|||
# Yarn Integrity file |
|||
.yarn-integrity |
|||
|
|||
# dotenv environment variables file |
|||
.env |
|||
.env.test |
|||
.env.production |
|||
|
|||
# parcel-bundler cache (https://parceljs.org/) |
|||
.cache |
|||
.parcel-cache |
|||
|
|||
# Next.js build output |
|||
.next |
|||
out |
|||
|
|||
# Nuxt.js build / generate output |
|||
.nuxt |
|||
dist |
|||
|
|||
# Gatsby files |
|||
.cache/ |
|||
# Comment in the public line in if your project uses Gatsby and not Next.js |
|||
# https://nextjs.org/blog/next-9-1#public-directory-support |
|||
# public |
|||
|
|||
# vuepress build output |
|||
.vuepress/dist |
|||
|
|||
# Serverless directories |
|||
.serverless/ |
|||
|
|||
# FuseBox cache |
|||
.fusebox/ |
|||
|
|||
# DynamoDB Local files |
|||
.dynamodb/ |
|||
|
|||
# TernJS port file |
|||
.tern-port |
|||
|
|||
# Stores VSCode versions used for testing VSCode extensions |
|||
.vscode-test |
|||
|
|||
# yarn v2 |
|||
.yarn/cache |
|||
.yarn/unplugged |
|||
.yarn/build-state.yml |
|||
.yarn/install-state.gz |
|||
.pnp.* |
|||
|
|||
**/.idea/ |
|||
**/target/ |
|||
*.iml |
|||
*.ipr |
|||
*.iws |
|||
*.log |
|||
|
|||
### VS Code ### |
|||
.vscode/ |
|||
.hbuilderx/ |
|||
unpackage/ |
@ -0,0 +1,154 @@ |
|||
<script> |
|||
export default { |
|||
globalData: { |
|||
|
|||
// baseURL: "http://127.0.0.1:8111/", |
|||
// wxSilentLoginURL: "http://127.0.0.1:8111/portal/v1/sysUser/wxSilentLogin", |
|||
// wxAuthLoginURL: "http://127.0.0.1:8111/portal/v1/sysUser/wxAuthLogin", |
|||
baseURL: "https://gateway.yyundong.com/", |
|||
wxSilentLoginURL: "https://gateway.yyundong.com/portal/v1/sysUser/wxSilentLogin", |
|||
wxAuthLoginURL: "https://gateway.yyundong.com/portal/v1/sysUser/wxAuthLogin", |
|||
isDebug: true, |
|||
token: "", |
|||
isLogin: false, |
|||
sysUserSid: "", |
|||
isOnLunch: false, |
|||
statusBarHeight: "", |
|||
navHeight: "", |
|||
navWidth: "", |
|||
totalHeight: "", |
|||
statusBarHeightPx: "", |
|||
navHeightPx: "", |
|||
navWidthPx: "", |
|||
totalHeightPx: "", |
|||
statusBarHeightUpx: "", |
|||
navHeightUpx: "", |
|||
navWidthUpx: "", |
|||
totalHeightUpx: "", |
|||
windowWidth: "", |
|||
isAuthentication: false, |
|||
gameCache: { |
|||
gameData: { |
|||
"isCache": false |
|||
}, |
|||
cache: false |
|||
} |
|||
}, |
|||
onLaunch: function() { |
|||
|
|||
this.WxSilentLogin() |
|||
let sysUserSid = this.ReadPreference("sysUserSid") |
|||
if (sysUserSid != null && sysUserSid.length != 0) { |
|||
this.globalData.sysUserSid = sysUserSid |
|||
} |
|||
let isLogin = this.ReadPreference("isLogin") |
|||
if (isLogin != null && isLogin == 1) { |
|||
this.globalData.isLogin = isLogin |
|||
} |
|||
let token = this.ReadPreference("token") |
|||
if (token != null && token.length != 0) { |
|||
this.globalData.token = token |
|||
} |
|||
|
|||
console.log('用户Sid', this.globalData.sysUserSid) |
|||
console.log('是否登陆', this.globalData.isLogin) |
|||
console.log('App', 'onLaunch') |
|||
// 不要删除 |
|||
console.log('高德地图定位Web服务key', '59970402d1c3f7dc1efff17d4dfcff21') |
|||
this.globalData.isOnLunch = true |
|||
let cache = this.ReadPreference("cache") |
|||
if (cache != null && cache.length != 0) { |
|||
this.globalData.gameCache.cache = cache |
|||
} else { |
|||
this.globalData.gameCache.cache = false |
|||
} |
|||
if (this.globalData.gameCache.cache) { |
|||
let gameCache = this.ReadPreference2("cacheData") |
|||
if (gameCache == undefined || gameCache == null || gameCache === "" || gameCache === "null") { |
|||
this.globalData.gameCache.cache = false |
|||
} else { |
|||
gameCache.isCache = true |
|||
this.globalData.gameCache.gameData = gameCache |
|||
} |
|||
} else { |
|||
this.globalData.gameCache.gameData = { |
|||
"isCache": false |
|||
} |
|||
} |
|||
|
|||
// 获取状态栏高度 |
|||
let info = uni.getSystemInfoSync() |
|||
// 状态栏高度 赋值给全局 |
|||
this.globalData.statusBarHeight = info.statusBarHeight |
|||
|
|||
this.globalData.windowWidth = info.windowWidth |
|||
this.globalData.windowWidthPx = info.windowWidth + 'px' |
|||
//console.log("屏幕原始宽度" + this.globalData.windowWidthPx) |
|||
|
|||
// 默认导航栏高度 |
|||
this.globalData.navHeight = '45' |
|||
// 默认导航栏宽度 |
|||
this.globalData.navWidth = this.globalData.windowWidth |
|||
// 默认导航栏总高度 |
|||
this.globalData.totalHeight = 45 + this.globalData.statusBarHeight |
|||
|
|||
// 获取胶囊高度 |
|||
// #ifdef MP-WEIXIN||MP-BAID||MP-QQ||MP-TOUTIAO |
|||
let menuButton = uni.getMenuButtonBoundingClientRect() |
|||
let top = menuButton.top |
|||
let bottom = menuButton.bottom |
|||
let navHeight = bottom - top + (top - this.globalData.statusBarHeight) * 2 + 4 |
|||
this.globalData.navHeight = navHeight |
|||
this.globalData.navWidth = menuButton.left |
|||
this.globalData.totalHeight = navHeight + this.globalData.statusBarHeight |
|||
// #endif |
|||
|
|||
//console.log('计算导航栏可用宽度', this.globalData.navWidth) |
|||
//console.log('计算导航栏可用高度', this.globalData.navHeight) |
|||
//console.log('计算状态栏高度', this.globalData.statusBarHeight) |
|||
//console.log('计算导航栏总高度', this.globalData.totalHeight) |
|||
|
|||
this.globalData.navWidthPx = this.globalData.navWidth + 'px' |
|||
this.globalData.navHeightPx = this.globalData.navHeight + 'px' |
|||
this.globalData.statusBarHeightPx = this.globalData.statusBarHeight + 'px' |
|||
this.globalData.totalHeightPx = this.globalData.totalHeight + 'px' |
|||
|
|||
//console.log('计算导航栏可用宽度像素值', this.globalData.navWidthPx) |
|||
//console.log('计算导航栏可用高度像素值', this.globalData.navHeightPx) |
|||
//console.log('计算状态栏高度像素值', this.globalData.statusBarHeightPx) |
|||
//console.log('计算导航栏总高度像素值', this.globalData.totalHeightPx) |
|||
|
|||
this.globalData.navWidthUpx = this.Px2Upx(this.globalData.windowWidth, this.globalData.navWidth) |
|||
this.globalData.navHeightUpx = this.Px2Upx(this.globalData.windowWidth, this.globalData.navHeight) |
|||
this.globalData.statusBarHeightUpx = this.Px2Upx(this.globalData.windowWidth, this.globalData |
|||
.statusBarHeight) |
|||
this.globalData.totalHeightUpx = this.Px2Upx(this.globalData.windowWidth, this.globalData.totalHeight) |
|||
|
|||
|
|||
//console.log('计算导航栏可用宽度upx', this.globalData.navWidthUpx) |
|||
//console.log('计算导航栏可用高度upx', this.globalData.navHeightUpx) |
|||
//console.log('计算状态栏高度upx', this.globalData.statusBarHeightUpx) |
|||
//console.log('计算导航栏总高度upx', this.globalData.totalHeightUpx) |
|||
|
|||
// 可能会报错 一定要放在最后执行 |
|||
// #ifdef APP-PLUS |
|||
plus.navigator.closeSplashscreen(); |
|||
// #endif |
|||
}, |
|||
onShow: function() { |
|||
//console.log('App Show') |
|||
}, |
|||
onHide: function() { |
|||
//console.log('App Hide') |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
/*每个页面公共css */ |
|||
/*每个页面公共css */ |
|||
page { |
|||
height: 100%; |
|||
width: 100%; |
|||
} |
|||
</style> |
@ -0,0 +1,32 @@ |
|||
const back = () => { |
|||
uni.navigateBack({ |
|||
delta: 1 |
|||
}) |
|||
} |
|||
|
|||
const onActivityResult = () => { |
|||
var pages = getCurrentPages(); |
|||
var currPage = pages[pages.length - 1]; //当前页面
|
|||
let res = currPage.data; |
|||
return res.onActivityResult |
|||
} |
|||
|
|||
// 返回数据到onShow()方法,取值 onActivityResult
|
|||
const setResult = (options) => { |
|||
|
|||
var pages = getCurrentPages(); |
|||
var prevPage = pages[pages.length - 2]; //上一个页面
|
|||
//直接调用上一个页面的setData()方法,把数据存到上一个页面中去
|
|||
// 上一个页面最后设置userdata
|
|||
let res = prevPage.data; |
|||
res.onActivityResult = options |
|||
uni.navigateBack({ //返回
|
|||
delta: 1 |
|||
}) |
|||
} |
|||
|
|||
export { |
|||
back, |
|||
setResult, |
|||
onActivityResult |
|||
} |
@ -0,0 +1,79 @@ |
|||
const chooseUpload = (num, url, type, formData) => { |
|||
|
|||
if (num == undefined || num == null || num == "") |
|||
num = 1 |
|||
|
|||
if (url == undefined || url == null || url == "") |
|||
url = "aos/file/batchUploadImage" |
|||
|
|||
if (type == undefined || type == null || type == "") |
|||
type = "files" |
|||
|
|||
if (formData == undefined || formData == null || formData == "") |
|||
formData = {} |
|||
|
|||
let totalImgs = 0; |
|||
let httpUrl = []; |
|||
|
|||
return new Promise((resolve) => { |
|||
|
|||
uni.chooseImage({ |
|||
count: num, //默认1
|
|||
sizeType: ['compressed'], //可以指定是原图还是压缩图,默认二者都有
|
|||
sourceType: ['album'], //从相册选择
|
|||
success: function(res) { |
|||
|
|||
let chooseImgs = res.tempFilePaths; |
|||
totalImgs = chooseImgs.length; |
|||
console.log("chooseImgs", res.tempFilePaths) |
|||
if (totalImgs > 0) |
|||
uni.showLoading({ |
|||
title: '请稍后...', |
|||
mask: true |
|||
}); |
|||
|
|||
for (var i = 0; i < chooseImgs.length; i++) { |
|||
uni.uploadFile({ |
|||
url: getApp().globalData.baseURL + url, //仅为示例,非真实的接口地址
|
|||
filePath: chooseImgs[i], |
|||
name: type, |
|||
formData: formData, |
|||
success: (uploadFileRes) => { |
|||
let data = JSON.parse(uploadFileRes.data) |
|||
console.log("data", data) |
|||
if (data.success) { |
|||
if (data.data != undefined && data.data.length != 0) |
|||
// 必须用变量去接受
|
|||
httpUrl = httpUrl.concat(data.data) |
|||
} |
|||
|
|||
}, |
|||
fail: (err) => { |
|||
console.log("err-->", err) |
|||
}, |
|||
complete: () => { |
|||
totalImgs--; |
|||
if (totalImgs <= 0) { |
|||
uni.hideLoading(); |
|||
resolve({ |
|||
"num": chooseImgs.length, |
|||
"urls": httpUrl |
|||
}) |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
}, |
|||
fail: function(res) { |
|||
resolve({ |
|||
"num": 0, |
|||
"urls": [] |
|||
}) |
|||
} |
|||
}); |
|||
}) |
|||
|
|||
} |
|||
|
|||
export default chooseUpload |
@ -0,0 +1,37 @@ |
|||
/** |
|||
* @desc 根据两点间的经纬度计算距离 |
|||
* @param float $lat 纬度值 |
|||
* @param float $lng 经度值 |
|||
*/ |
|||
const getDistance = (lat1, lon1, lat2, lon2) => { |
|||
console.log(lat1) |
|||
console.log(lon1) |
|||
console.log(lat2) |
|||
console.log(lon2) |
|||
let ew1, ns1, ew2, ns2; |
|||
let distance; |
|||
// 角度转换为弧度
|
|||
ew1 = lon1 * 0.01745329252; |
|||
ns1 = lat1 * 0.01745329252; |
|||
ew2 = lon2 * 0.01745329252; |
|||
ns2 = lat2 * 0.01745329252; |
|||
distance = distance = Math.sin(ns1) * Math.sin(ns2) + Math.cos(ns1) * Math.cos(ns2) * |
|||
Math.cos(ew1 - ew2); |
|||
// 调整到[-1..1]范围内,避免溢出
|
|||
if (distance > 1.0) |
|||
distance = 1.0; |
|||
else if (distance < -1.0) |
|||
distance = -1.0; |
|||
// 求大圆劣弧长度
|
|||
distance = 6370693.5 * Math.acos(distance); |
|||
let isBig = false; // 是否为大于等于1000m
|
|||
if (distance >= 1000) { |
|||
distance /= 1000; |
|||
isBig = true; |
|||
} |
|||
|
|||
return distance.toFixed(2) + (isBig ? "km" : "m"); |
|||
|
|||
} |
|||
|
|||
export default getDistance |
@ -0,0 +1,72 @@ |
|||
const writeGameCahce = (gameData) => { |
|||
gameData.isCache = true |
|||
uni.setStorageSync("cache", true); |
|||
uni.setStorageSync("cacheData", gameData); |
|||
console.log('保存game缓存成功') |
|||
} |
|||
|
|||
const clearGameCache = (gameData) => { |
|||
if (gameData.isCache) { |
|||
uni.setStorageSync("cache", false); |
|||
uni.setStorageSync("cacheData", {}); |
|||
console.log('清除game缓存成功') |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 使用读取时 最好 hasCache() 返回True 在进行调用 |
|||
*/ |
|||
const readGameCahce = () => { |
|||
|
|||
let value |
|||
try { |
|||
value = uni.getStorageSync("cacheData"); |
|||
if (value) { |
|||
console.log(value); |
|||
} |
|||
} catch (e) { |
|||
// error
|
|||
value = null |
|||
console.log("error"); |
|||
} |
|||
|
|||
if (value == undefined || value == null || value === "" || value === "null") { |
|||
return {}; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
/** |
|||
* 是否有缓存 |
|||
*/ |
|||
const hasCache = () => { |
|||
|
|||
let value |
|||
try { |
|||
value = uni.getStorageSync("cache"); |
|||
if (value) { |
|||
console.log(value); |
|||
} |
|||
} catch (e) { |
|||
// error
|
|||
value = null |
|||
console.log("error"); |
|||
} |
|||
|
|||
if (value == undefined || value == null || value === "" || value === "null") { |
|||
return false; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
export { |
|||
writeGameCahce, |
|||
clearGameCache, |
|||
readGameCahce, |
|||
hasCache |
|||
} |
@ -0,0 +1,465 @@ |
|||
const baseImgURL = 'https://www.ourpyw.com/static/uni-app/'; |
|||
|
|||
import { |
|||
isEmpty |
|||
} from './TextUtils.js' |
|||
/** |
|||
* 通用网络请求 |
|||
* loading: 是否显示网络请求时的loading,默认不提示【允许不传值】 |
|||
* showError: 是否显示错误信息,默认显示(如有特殊code需要自己判断逻辑时,请改成false)【允许不传值】 |
|||
* method: 网络请求方式 GET/POST/PUT【禁止不传值】 |
|||
* paramsType: 网络请求传参方式 JSON/FORM【禁止不传值】 |
|||
* autoUrl: 自动拼接网址,默认是 getApp().globalData.baseURL+ 相对地址【允许不传值】false认为非本项目域名是第三方接口 |
|||
* 响应: |
|||
* 响应到success则代表网络请求成功,直接返回对象,需自行判断逻辑 |
|||
* 响应到fail则代表无网络或无连接情况 |
|||
*/ |
|||
const http = (options) => { |
|||
|
|||
console.log(getApp().globalData.token) |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http网络请求info', options) |
|||
} |
|||
|
|||
if (isEmpty(options.loading)) { |
|||
// 显示loading(默认不显示loading)
|
|||
options.loading = false |
|||
} |
|||
|
|||
if (isEmpty(options.showError)) { |
|||
// 失败弹出提示(默认提示错误信息)
|
|||
options.showError = true |
|||
} |
|||
// if (isEmpty(options.method) || (options.method != "POST" && options.method != "GET"&& options.method != "PUT")) {
|
|||
if (isEmpty(options.method) || (options.method != "POST" && options.method != "GET" && options.method != "PUT" && options.method != "DELETE")) { |
|||
uni.showToast({ |
|||
title: "网络请求 'method' 为 POST/GET/PUT", |
|||
icon: 'none', |
|||
duration: 4000 |
|||
}) |
|||
return |
|||
} |
|||
|
|||
if ("JSON" == options.paramsType) { |
|||
options.paramsType = "application/json" |
|||
} else if ("FORM" == options.paramsType) { |
|||
options.paramsType = "application/x-www-form-urlencoded" |
|||
} else { |
|||
uni.showToast({ |
|||
title: "网络请求 'paramsType' 为 JSON/FORM", |
|||
icon: 'none', |
|||
duration: 4000 |
|||
}) |
|||
return |
|||
} |
|||
|
|||
if (isEmpty(options.autoUrl)) { |
|||
// 默认拼接网址
|
|||
options.autoUrl = true |
|||
} |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
|
|||
if (options.loading) { |
|||
uni.showLoading({ |
|||
title: '加载中...', |
|||
// 默认遮罩出现可以继续操作
|
|||
mask: options.load || true |
|||
}); |
|||
} |
|||
|
|||
var url = options.autoUrl ? getApp().globalData.baseURL + options.url : options.url |
|||
|
|||
uni.request({ |
|||
// 组装请求地址
|
|||
url: url, |
|||
// 请求方式 GET POST
|
|||
method: options.method, |
|||
header: { |
|||
// 传参方式
|
|||
'content-type': options.paramsType, |
|||
'token': getApp().globalData.token |
|||
}, |
|||
// 具体参数
|
|||
data: options.data, |
|||
success: res => { |
|||
|
|||
// 关闭显示框
|
|||
uni.hideLoading(); |
|||
|
|||
console.log(res) |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http网络路径', url) |
|||
console.log('Http网络请求结果', JSON.parse(JSON.stringify(res.data))) |
|||
} |
|||
|
|||
if (res.statusCode == 200) { |
|||
// 自动拼接类型,认为是公司后台提供接口服务,按照规定标准进行检查返回内容
|
|||
|
|||
if (options.autoUrl) { |
|||
|
|||
// 进行success字段检查
|
|||
|
|||
if (!res.data.success) { |
|||
|
|||
|
|||
if ("5000" == res.data.code) { |
|||
|
|||
uni.setStorageSync("memberSid", null); |
|||
getApp().globalData.isLogin = false |
|||
getApp().globalData.memberSid = null |
|||
|
|||
uni.showToast({ |
|||
title: "出错了,请重试", |
|||
// 保证文字长度
|
|||
icon: "none", |
|||
duration: 3000 |
|||
}) |
|||
|
|||
return |
|||
|
|||
} else { |
|||
// 错误提示
|
|||
if (options.showError) { |
|||
|
|||
if (isEmpty(res.data)) { |
|||
// 未成功获取到服务器返回的json
|
|||
uni.showToast({ |
|||
// 不能超过7个字
|
|||
title: "服务器响应为空", |
|||
icon: "error" |
|||
}) |
|||
} else { |
|||
let errorMsg = res.data.msg |
|||
|
|||
if (isEmpty(errorMsg)) { |
|||
errorMsg = url + "服务器未返回错误信息" |
|||
} |
|||
|
|||
uni.showToast({ |
|||
title: errorMsg, |
|||
// 保证文字长度
|
|||
icon: "none", |
|||
duration: 3000 |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
// 直接返回Response
|
|||
resolve(res.data) |
|||
} else { |
|||
|
|||
uni.showToast({ |
|||
title: res.statusCode + ":" + res.data.error, |
|||
// 保证文字长度
|
|||
icon: "none", |
|||
duration: 3000 |
|||
}) |
|||
|
|||
// 返回错误数据
|
|||
reject(res.data.error) |
|||
} |
|||
|
|||
}, |
|||
fail: (err) => { |
|||
// 关闭显示框
|
|||
uni.hideLoading(); |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log("Http网络请求fail", err) |
|||
} |
|||
|
|||
uni.showToast({ |
|||
title: '请检查网络', |
|||
icon: 'error' |
|||
}) |
|||
// 返回错误数据
|
|||
reject(err) |
|||
}, |
|||
complete: () => { |
|||
|
|||
} |
|||
}); |
|||
|
|||
}) |
|||
} |
|||
|
|||
const upload = (options) => { |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http网络请求info', options) |
|||
} |
|||
|
|||
if (options.loading == undefined || options.loading == null || options.loading === "") { |
|||
options.loading = false |
|||
} |
|||
|
|||
|
|||
if (options.key == undefined || options.key == null || options.key === "") { |
|||
options.key = "file" |
|||
} |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
|
|||
if (options.loading) { |
|||
uni.showLoading({ |
|||
title: '加载中...', |
|||
mask: options.load || true // 默认遮罩出现可以继续操作
|
|||
}); |
|||
} |
|||
|
|||
try { |
|||
uni.uploadFile({ |
|||
|
|||
url: getApp().globalData.baseURL + options.url, |
|||
filePath: options.filePath, |
|||
name: options.key, |
|||
formData: options.data, |
|||
|
|||
success: (res) => { |
|||
uni.hideLoading(); |
|||
|
|||
// 不知道什么谜之操作 需要手动转换成json对象
|
|||
res.data = JSON.parse(res.data) |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http网络路径', url) |
|||
console.log('Http网络请求结果', JSON.stringify(res.data)) |
|||
} |
|||
|
|||
if (res.data.success) { |
|||
|
|||
// 成功提示
|
|||
if (options.toast) { |
|||
uni.showToast({ |
|||
title: res.data.msg, |
|||
icon: "none" |
|||
}) |
|||
} |
|||
|
|||
// 成功数据返回
|
|||
resolve(res.data) |
|||
} else { |
|||
|
|||
// 错误提示
|
|||
uni.showToast({ |
|||
title: res.data.msg, |
|||
icon: "none" |
|||
}) |
|||
// 返回错误数据
|
|||
reject(res.data) |
|||
} |
|||
|
|||
}, |
|||
fail: (err) => { |
|||
uni.hideLoading(); |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log(err) |
|||
} |
|||
|
|||
uni.showToast({ |
|||
title: '请检查网络连接', |
|||
icon: 'none' |
|||
}) |
|||
|
|||
// 返回错误数据
|
|||
reject(err) |
|||
}, |
|||
complete: () => { |
|||
|
|||
} |
|||
}); |
|||
} catch (e) { |
|||
uni.hideLoading(); |
|||
uni.showToast({ |
|||
title: '服务端异常', |
|||
icon: 'none' |
|||
}) |
|||
|
|||
// 返回错误数据
|
|||
reject(err) |
|||
} |
|||
|
|||
}) |
|||
} |
|||
|
|||
const httpCookie = (options) => { |
|||
|
|||
if (isDebug) { |
|||
console.log('Http网络请求info', options) |
|||
} |
|||
|
|||
if (options.loading == undefined || options.loading == null || options.loading === "") { |
|||
options.loading = false |
|||
} |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
|
|||
if (options.loading) { |
|||
uni.showLoading({ |
|||
title: '加载中...', |
|||
mask: options.load || true // 默认遮罩出现可以继续操作
|
|||
}); |
|||
} |
|||
|
|||
try { |
|||
uni.request({ |
|||
|
|||
url: baseURL + options.url, |
|||
method: 'POST', |
|||
header: { |
|||
'content-type': 'application/x-www-form-urlencoded', |
|||
|
|||
// #ifdef MP-WEIXIN
|
|||
'Cookie': options.cookie |
|||
// #endif
|
|||
|
|||
}, |
|||
data: options.data, |
|||
|
|||
success: res => { |
|||
uni.hideLoading(); |
|||
|
|||
if (isDebug) { |
|||
console.log('Http网络路径', url) |
|||
console.log('Http网络请求结果', res.data) |
|||
} |
|||
|
|||
if (res.data.success) { |
|||
|
|||
// 成功提示
|
|||
if (options.toast) { |
|||
uni.showToast({ |
|||
title: res.data.msg, |
|||
icon: "none" |
|||
}) |
|||
} |
|||
|
|||
// 成功数据返回
|
|||
resolve(res.data) |
|||
} else { |
|||
|
|||
// 错误提示
|
|||
uni.showToast({ |
|||
title: res.data.msg, |
|||
icon: "none" |
|||
}) |
|||
// 返回错误数据
|
|||
reject(res.data) |
|||
} |
|||
|
|||
}, |
|||
fail: (err) => { |
|||
uni.hideLoading(); |
|||
|
|||
if (isDebug) { |
|||
console.log(err) |
|||
} |
|||
|
|||
uni.showToast({ |
|||
title: '请检查网络连接', |
|||
icon: 'none' |
|||
}) |
|||
|
|||
// 返回错误数据
|
|||
reject(err) |
|||
}, |
|||
complete: () => { |
|||
|
|||
} |
|||
}); |
|||
} catch (e) { |
|||
uni.hideLoading(); |
|||
uni.showToast({ |
|||
title: '服务端异常', |
|||
icon: 'none' |
|||
}) |
|||
|
|||
// 返回错误数据
|
|||
reject(err) |
|||
} |
|||
|
|||
}) |
|||
} |
|||
|
|||
// 加载其他网站的地址
|
|||
const httpOtherUrl = (options) => { |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http第三方网络请求info', options) |
|||
} |
|||
|
|||
if (options.loading == undefined || options.loading == null || options.loading === "") { |
|||
options.loading = false |
|||
} |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
|
|||
if (options.loading) { |
|||
uni.showLoading({ |
|||
title: '加载中...', |
|||
mask: options.load || true // 默认遮罩出现可以继续操作
|
|||
}); |
|||
} |
|||
|
|||
try { |
|||
uni.request({ |
|||
url: options.url, |
|||
method: 'GET', |
|||
data: options.data, |
|||
success: res => { |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http第三方网络请求结果', res.data) |
|||
} |
|||
|
|||
uni.hideLoading(); |
|||
resolve(res.data) |
|||
}, |
|||
fail: (err) => { |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http第三方网络请求错误', err) |
|||
} |
|||
|
|||
uni.hideLoading(); |
|||
|
|||
reject(err) |
|||
|
|||
uni.showToast({ |
|||
title: '请检查网络连接', |
|||
icon: 'none' |
|||
}) |
|||
}, |
|||
complete: () => { |
|||
|
|||
} |
|||
}); |
|||
} catch (e) { |
|||
uni.hideLoading(); |
|||
|
|||
reject(e) |
|||
|
|||
uni.showToast({ |
|||
title: '服务端异常', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
|
|||
}) |
|||
} |
|||
export { |
|||
http, |
|||
httpOtherUrl, |
|||
baseImgURL, |
|||
httpCookie, |
|||
upload |
|||
} |
@ -0,0 +1,48 @@ |
|||
const write = (keyStr, keyValue) => { |
|||
uni.setStorageSync(keyStr, keyValue); |
|||
console.log('保存' + keyStr + '成功') |
|||
} |
|||
|
|||
const write2 = (keyStr, objectValue) => { |
|||
uni.setStorageSync(keyStr, objectValue); |
|||
console.log('保存' + keyStr + '成功') |
|||
} |
|||
|
|||
const read = (keyStr) => { |
|||
|
|||
let value |
|||
try { |
|||
value = uni.getStorageSync(keyStr); |
|||
if (value) { |
|||
console.log(value); |
|||
} |
|||
} catch (e) { |
|||
// error
|
|||
value = null |
|||
console.log("error"); |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
const read2 = (keyStr) => { |
|||
|
|||
let value |
|||
try { |
|||
value = uni.getStorageSync(keyStr); |
|||
if (value) { |
|||
console.log(value); |
|||
} |
|||
} catch (e) { |
|||
// error
|
|||
value = null |
|||
console.log("error"); |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
export { |
|||
write, |
|||
read, |
|||
write2, |
|||
read2 |
|||
} |
@ -0,0 +1,5 @@ |
|||
const px2Upx = (windowWidth, px) => { |
|||
return px / (windowWidth / 750) |
|||
} |
|||
|
|||
export default px2Upx |
@ -0,0 +1,11 @@ |
|||
const isEmpty = (obj) => { |
|||
if (obj == undefined || obj == null || obj === "" || obj === "null") { |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
export { |
|||
isEmpty |
|||
} |
@ -0,0 +1,201 @@ |
|||
const timeText = (time, format) => { |
|||
|
|||
if (format == null) { |
|||
format = "yyyy-MM-dd HH:mm:ss" |
|||
} |
|||
|
|||
let timeStr = ""; |
|||
|
|||
let todayEndTime = getTodayEndTime(); |
|||
let todayStartTime = getTodayStartTime(); |
|||
let thisWeekStartTime = getThisWeekStartTime(); |
|||
|
|||
console.log("kaishi" + todayStartTime) |
|||
console.log("jieshu" + thisWeekStartTime) |
|||
|
|||
|
|||
// 今天23:59:59:999之后
|
|||
if (time > todayEndTime) { |
|||
// 显示年月日
|
|||
timeStr = timeFormat(time, format) |
|||
} else if (thisWeekStartTime > time) { |
|||
// 此周前(本周星期一之前)
|
|||
|
|||
let i = format.indexOf(" "); |
|||
let formatStyle = format.substring(i + 1); |
|||
let formatNew = timeFormat(time, formatStyle); |
|||
|
|||
if (time > todayStartTime - 86399999 && time < todayStartTime) { |
|||
// 显示昨天
|
|||
timeStr = "昨天 " + formatNew; |
|||
} else { |
|||
timeStr = timeFormat(time, format) |
|||
} |
|||
|
|||
} else { |
|||
// 显示星期 时分
|
|||
let i = format.indexOf(" "); |
|||
let formatStyle = format.substring(i + 1); |
|||
let formatNew = timeFormat(time, formatStyle); |
|||
|
|||
if (todayStartTime - 86399999 > time) { |
|||
// 显示星期
|
|||
timeStr = getWeekStr(time) + " " + formatNew |
|||
} else if (time > todayStartTime - 86399999 && time < todayStartTime) { |
|||
// 显示昨天
|
|||
timeStr = "昨天 " + formatNew; |
|||
} else { |
|||
// 显示
|
|||
timeStr = formatNew; |
|||
} |
|||
} |
|||
|
|||
return timeStr |
|||
} |
|||
|
|||
/** |
|||
* 获取本周开始时间 |
|||
*/ |
|||
const getThisWeekStartTime = () => { |
|||
|
|||
let todayEndTime = getTodayEndTime(); |
|||
|
|||
var date = new Date(); |
|||
var weekDays = date.getDay(); |
|||
var weeks = new Array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"); |
|||
var week = weeks[weekDays]; |
|||
|
|||
return todayEndTime + 1 - weekDays * 86400000; |
|||
} |
|||
|
|||
/** |
|||
* 获取今天是周几 |
|||
*/ |
|||
const getWeekStr = (time) => { |
|||
|
|||
|
|||
if (time instanceof Date) { |
|||
|
|||
} else { |
|||
let temp = new Date(time); |
|||
time = temp; |
|||
} |
|||
|
|||
let todayEndTime = getTodayEndTime(); |
|||
|
|||
var weekDays = time.getDay(); |
|||
var weeks = new Array("星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"); |
|||
var week = weeks[weekDays]; |
|||
|
|||
return week; |
|||
} |
|||
|
|||
/** |
|||
* 获取今天开始时间 |
|||
*/ |
|||
const getTodayStartTime = () => { |
|||
|
|||
let startTime = 0; |
|||
|
|||
let myDate = new Date(); |
|||
|
|||
let current = myDate.getTime(); |
|||
|
|||
try { |
|||
|
|||
let format = timeFormat(myDate); |
|||
let split = format.split(" "); |
|||
let time = split[1].split(":"); |
|||
|
|||
startTime = current - time[0] * 60 * 60 * 1000 - time[1] * 60 * 1000 - time[2] * 1000 - time[3] |
|||
|
|||
} catch (e) { |
|||
console.log(e) |
|||
} |
|||
|
|||
return startTime; |
|||
} |
|||
|
|||
/** |
|||
* 获取今天结束时间 |
|||
*/ |
|||
const getTodayEndTime = () => { |
|||
|
|||
let endTime = 0; |
|||
|
|||
let myDate = new Date(); |
|||
|
|||
let current = myDate.getTime(); |
|||
|
|||
try { |
|||
|
|||
let format = timeFormat(myDate); |
|||
let split = format.split(" "); |
|||
let time = split[1].split(":"); |
|||
|
|||
let startTime = current - time[0] * 60 * 60 * 1000 - time[1] * 60 * 1000 - time[2] * 1000 - time[3] |
|||
endTime = startTime + 86399999; |
|||
|
|||
} catch (e) { |
|||
console.log(e) |
|||
} |
|||
|
|||
return endTime; |
|||
} |
|||
|
|||
/** |
|||
* 格式化时间 |
|||
* 支持时间戳 以及 date类型 |
|||
*/ |
|||
const timeFormat = (date, formoat) => { |
|||
|
|||
if (date == undefined || date == null) { |
|||
return ""; |
|||
} |
|||
|
|||
if (date instanceof Date) { |
|||
console.log("正确") |
|||
} else { |
|||
let temp = new Date(Number(date)); |
|||
date = temp; |
|||
} |
|||
|
|||
let fmt = formoat; |
|||
|
|||
if (fmt == null) { |
|||
fmt = "yyyy-MM-dd HH:mm:ss:SSS" |
|||
} |
|||
|
|||
|
|||
let ret; |
|||
const opt = { |
|||
"y+": date.getFullYear().toString(), // 年
|
|||
"M+": (date.getMonth() + 1).toString(), // 月
|
|||
"d+": date.getDate().toString(), // 日
|
|||
"H+": date.getHours().toString(), // 时
|
|||
"m+": date.getMinutes().toString(), // 分
|
|||
"s+": date.getSeconds().toString(), // 秒
|
|||
"S+": date.getMilliseconds().toString() |
|||
|
|||
}; |
|||
for (let k in opt) { |
|||
ret = new RegExp("(" + k + ")").exec(fmt); |
|||
if (ret) { |
|||
fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0"))) |
|||
}; |
|||
}; |
|||
return fmt; |
|||
} |
|||
|
|||
const currentMillions = () => { |
|||
let current = new Date().getTime(); |
|||
return current; |
|||
} |
|||
|
|||
export { |
|||
timeText, |
|||
timeFormat, |
|||
currentMillions, |
|||
getWeekStr, |
|||
getTodayEndTime |
|||
} |
@ -0,0 +1,8 @@ |
|||
const toast = (msg) => { |
|||
uni.showToast({ |
|||
title: msg, |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
|
|||
export default toast |
@ -0,0 +1,12 @@ |
|||
const putWEBExtra = (url) => { |
|||
return encodeURIComponent(url) |
|||
} |
|||
|
|||
const getWEBExtra = (options) => { |
|||
return decodeURIComponent(options.url) |
|||
} |
|||
|
|||
export { |
|||
putWEBExtra, |
|||
getWEBExtra |
|||
} |
@ -0,0 +1,353 @@ |
|||
/** |
|||
* @说明:工具集 |
|||
* @作者:陈万照 |
|||
* @公司:山东标梵互动技术有限公司 |
|||
* @官网:http://biaofun.com/
|
|||
* @版本:v1.0.0 |
|||
* @时间:2020年4月28日11:28:13 |
|||
*/ |
|||
export default { |
|||
/** |
|||
* 同步 try catch 的进一步封装处理 |
|||
* 使用方法: |
|||
* let [err, res] = await this.$utils.asyncTasks(Promise函数); |
|||
* if(res) 成功 |
|||
* if(err) 失败 |
|||
*/ |
|||
asyncTasks(promise) { |
|||
return promise.then(data => { |
|||
return [null, data]; |
|||
}).catch(err => [err]); |
|||
}, |
|||
|
|||
/** |
|||
* 精确判断数据是否是 Object 类型 |
|||
* @param {Any} val 要判断的数据 |
|||
* @returns {Boolean} true:是;false:不是; |
|||
*/ |
|||
isObject(val) { |
|||
return Object.prototype.toString.call(val) === '[object Object]' && val !== null && val !== undefined; |
|||
}, |
|||
|
|||
/** |
|||
* 判断数据是否是 Array 类型 |
|||
* @param {Any} val 要判断的数据 |
|||
* @returns {Boolean} true:是;false:不是; |
|||
*/ |
|||
isArray(val) { |
|||
return Object.prototype.toString.call(val) === '[object Array]'; |
|||
}, |
|||
|
|||
/** |
|||
* 判断数据是否是 String 类型 |
|||
* @param {Any} val 要判断的数据 |
|||
* @returns {Boolean} true:是;false:不是; |
|||
*/ |
|||
isString(val) { |
|||
return Object.prototype.toString.call(val) === '[object String]'; |
|||
}, |
|||
|
|||
/** |
|||
* 精确判断数据是否是 Date 类型 |
|||
* @param {Any} val 要判断的数据 |
|||
* @returns {Boolean} true:是;false:不是; |
|||
*/ |
|||
isDate(val) { |
|||
return Object.prototype.toString.call(val) === '[object Date]'; |
|||
}, |
|||
|
|||
/** |
|||
* 精确判断数据是否是 Function 类型 |
|||
* @param {Any} val 要判断的数据 |
|||
* @returns {Boolean} true:是;false:不是; |
|||
*/ |
|||
isFunction(val) { |
|||
return Object.prototype.toString.call(val) === '[object Function]'; |
|||
}, |
|||
|
|||
/** |
|||
* 精确判断数据是否是 Number 类型 |
|||
* @param {Any} val 要判断的数据 |
|||
* @returns {Boolean} true:是;false:不是; |
|||
*/ |
|||
isNumber(val) { |
|||
return Object.prototype.toString.call(val) === '[object Number]'; |
|||
}, |
|||
|
|||
/** |
|||
* 精确判断数据是否是 Boolean 类型 |
|||
* @param {Any} val 要判断的数据 |
|||
* @returns {Boolean} true:是;false:不是; |
|||
*/ |
|||
isBoolean(val) { |
|||
return Object.prototype.toString.call(val) === '[object Boolean]'; |
|||
}, |
|||
|
|||
/** |
|||
* 判断 URL 是否是绝对 URL。 |
|||
* @param {String} url 要判断的 URL |
|||
* @return {Boolean} true:是绝对URL;false:不是绝对URL; |
|||
*/ |
|||
isAbsoluteURL(url) { |
|||
// 如果 URL 以 “<scheme>://” 或 “//”(协议相对URL)开头,则认为它是绝对的
|
|||
// RFC 3986 将方案名称定义为以字母开头的字符序列,然后是字母,数字,加号,句点或连字符的任意组合
|
|||
return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); |
|||
}, |
|||
|
|||
/** |
|||
* 合并 baseURL 和相对 URL 成一个完整的 URL |
|||
* @param {String} baseURL baseURL |
|||
* @param {String} relativeURL 相对 URL |
|||
* @returns {String} 返回组合后的完整 URL |
|||
*/ |
|||
combineURLs(baseURL, relativeURL) { |
|||
return relativeURL && this.isString(relativeURL) && this.isString(baseURL) ? baseURL.replace(/\/+$/, '') + '/' + |
|||
relativeURL.replace(/^\/+/, '') : baseURL; |
|||
}, |
|||
|
|||
/** |
|||
* 深度合并对象,只支持合并两个对象,该方法不会改变原有的对象 |
|||
* @param {Object} FirstOBJ 第一个对象 |
|||
* @param {Object} SecondOBJ 第二个对象 |
|||
* @return {Object} 返回深度合并后的对象 |
|||
*/ |
|||
deepMargeObject(FirstOBJ, SecondOBJ) { |
|||
let ResultOBJ = {}; |
|||
for (let key in FirstOBJ) { |
|||
ResultOBJ[key] = ResultOBJ[key] && ResultOBJ[key].toString() === "[object Object]" ? this.deepMargeObject( |
|||
ResultOBJ[ |
|||
key], FirstOBJ[key]) : ResultOBJ[key] = FirstOBJ[key]; |
|||
} |
|||
for (let key in SecondOBJ) { |
|||
ResultOBJ[key] = ResultOBJ[key] && ResultOBJ[key].toString() === "[object Object]" ? this.deepMargeObject( |
|||
ResultOBJ[ |
|||
key], SecondOBJ[key]) : ResultOBJ[key] = SecondOBJ[key]; |
|||
} |
|||
return ResultOBJ; |
|||
}, |
|||
|
|||
/** |
|||
* 生成指定长度的随机字符串 |
|||
* @param {Number} min 最小程度 |
|||
* @param {Number} max 最大长度 |
|||
* @return {String} 返回生成的字符串 |
|||
*/ |
|||
randomString(min, max) { |
|||
let returnStr = "", |
|||
range = (max ? Math.round(Math.random() * (max - min)) + min : min), |
|||
arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', |
|||
'k', 'l', |
|||
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', |
|||
'H', 'I', |
|||
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' |
|||
]; |
|||
for (let i = 0; i < range; i++) { |
|||
let index = Math.round(Math.random() * (arr.length - 1)); |
|||
returnStr += arr[index]; |
|||
} |
|||
return returnStr; |
|||
}, |
|||
|
|||
/** |
|||
* 格式化日期 |
|||
* @param {Date|String} date 日期或日期字符串 |
|||
*/ |
|||
formatDate(date) { |
|||
let YYYY = null; |
|||
let M = null; |
|||
let MM = null; |
|||
let D = null; |
|||
let DD = null; |
|||
let h = null; |
|||
let hh = null; |
|||
let m = null; |
|||
let mm = null; |
|||
let s = null; |
|||
let ss = null; |
|||
let ms = null; |
|||
let ms2 = null; |
|||
let ms3 = null; |
|||
let ms4 = null; |
|||
let dt = null; |
|||
|
|||
// 如果 date 是 String 类型
|
|||
if (date && this.isString(date)) { |
|||
// 真机运行时,如果直接用 new Date('YYYY-MM-DD hh:mm:ss') 会报 Invalid Date 错误,所以采用下面的方式创建日期
|
|||
let dtArr = date.replace(/\//g, '.').replace(/-/g, '.').replace(/:/g, '.').replace(/T/g, ' ').replace(' ', |
|||
'.').replace( |
|||
'Z', '').split('.'); |
|||
|
|||
let year = 2020; |
|||
let month = 12; |
|||
let day = 18; |
|||
let hour = 0; |
|||
let minute = 0; |
|||
let second = 0; |
|||
let millisecond = 0; |
|||
|
|||
// 年
|
|||
if (dtArr.length > 0 && !isNaN(dtArr[0])) { |
|||
year = parseInt(dtArr[0]); |
|||
} |
|||
// 月
|
|||
if (dtArr.length > 1 && !isNaN(dtArr[1])) { |
|||
month = parseInt(dtArr[1]); |
|||
} |
|||
// 日
|
|||
if (dtArr.length > 2 && !isNaN(dtArr[2])) { |
|||
day = parseInt(dtArr[2]); |
|||
} |
|||
// 时
|
|||
if (dtArr.length > 3 && !isNaN(dtArr[3])) { |
|||
hour = parseInt(dtArr[3]); |
|||
} |
|||
// 分
|
|||
if (dtArr.length > 4 && !isNaN(dtArr[4])) { |
|||
minute = parseInt(dtArr[4]); |
|||
} |
|||
// 秒
|
|||
if (dtArr.length > 5 && !isNaN(dtArr[5])) { |
|||
second = parseInt(dtArr[5]); |
|||
} |
|||
// 毫秒
|
|||
if (dtArr.length > 6 && !isNaN(dtArr[6])) { |
|||
millisecond = parseInt(dtArr[6]); |
|||
} |
|||
|
|||
date = new Date(year, month - 1, day, hour, minute, second, millisecond); |
|||
} |
|||
|
|||
// 如果 date 是 Date 类型
|
|||
if (date && this.isDate(date)) { |
|||
YYYY = date.getFullYear(); |
|||
M = date.getMonth() + 1; |
|||
MM = M >= 10 ? M : '0' + M; |
|||
D = date.getDate(); |
|||
DD = D >= 10 ? D : '0' + D; |
|||
h = date.getHours(); |
|||
hh = h >= 10 ? h : '0' + h; |
|||
m = date.getMinutes(); |
|||
mm = m >= 10 ? m : '0' + m; |
|||
s = date.getSeconds(); |
|||
ss = s >= 10 ? s : '0' + s; |
|||
ms = date.getMilliseconds(); |
|||
ms2 = ms; |
|||
ms3 = ms; |
|||
ms4 = ms; |
|||
if (ms < 10) { |
|||
ms2 = '0' + ms; |
|||
ms3 = '00' + ms; |
|||
ms4 = '000' + ms; |
|||
} else if (ms < 100) { |
|||
ms3 = '0' + ms; |
|||
ms4 = '00' + ms; |
|||
} else { |
|||
ms4 = '0' + ms; |
|||
} |
|||
} |
|||
|
|||
// 返回的数据对象
|
|||
let result = { |
|||
YYYY: YYYY, |
|||
MM: MM, |
|||
M: M, |
|||
DD: DD, |
|||
D: D, |
|||
hh: hh, |
|||
h: h, |
|||
mm: mm, |
|||
m: m, |
|||
ss: ss, |
|||
s: s, |
|||
ms: ms, |
|||
ms2: ms2, |
|||
ms3: ms3, |
|||
ms4: ms4, |
|||
dt: date, |
|||
f1: `${YYYY}-${MM}-${DD}`, |
|||
f2: `${YYYY}年${M}月${D}日`, |
|||
f3: `${YYYY}-${M}-${D} ${hh}:${mm}`, |
|||
f4: `${h}:${m}:${s}`, |
|||
f5: `${MM}-${DD}`, |
|||
f6: `${YYYY}-${MM}`, |
|||
f7: `${YYYY}年${M}月`, |
|||
f8: `${h}:${m}`, |
|||
f9: `${M}月${D}日`, |
|||
notes: 'YYYY(年),MM(月,补0),M(月,不补0),DD(日,补0),D(日,不补0),hh(时,补0),h(时,不补0),mm(分,补0),m(分,不补0),ss(秒,补0),s(秒,不补0),ms(毫秒,不补0),ms2(毫秒,补0到2位),ms3(毫秒,补0到3位),ms4(毫秒,补0到4位),其余的f1,f2,... 看格式就知道了!' |
|||
}; |
|||
return result; |
|||
}, |
|||
|
|||
/** |
|||
* 数字转中文 |
|||
* @param {Number} num 数字 |
|||
*/ |
|||
numberToChinese(num) { |
|||
if (!/^\d*(\.\d*)?$/.test(num)) return "Number is wrong!"; |
|||
let AA = new Array("零", "一", "二", "三", "四", "五", "六", "七", "八", "九"); |
|||
let BB = new Array("", "十", "百", "千", "万", "亿", "点", ""); |
|||
let a = ("" + num).replace(/(^0*)/g, "").split("."), |
|||
k = 0, |
|||
re = ""; |
|||
for (let i = a[0].length - 1; i >= 0; i--) { |
|||
switch (k) { |
|||
case 0: |
|||
re = BB[7] + re; |
|||
break; |
|||
case 4: |
|||
if (!new RegExp("0{4}\\d{" + (a[0].length - i - 1) + "}$").test(a[0])) |
|||
re = BB[4] + re; |
|||
break; |
|||
case 8: |
|||
re = BB[5] + re; |
|||
BB[7] = BB[5]; |
|||
k = 0; |
|||
break; |
|||
} |
|||
if (k % 4 == 2 && a[0].charAt(i + 2) != 0 && a[0].charAt(i + 1) == 0) re = AA[0] + re; |
|||
if (a[0].charAt(i) != 0) re = AA[a[0].charAt(i)] + BB[k % 4] + re; |
|||
k++; |
|||
} |
|||
if (a.length > 1) //加上小数部分(如果有小数部分)
|
|||
{ |
|||
re += BB[6]; |
|||
for (let i = 0; i < a[1].length; i++) re += AA[a[1].charAt(i)]; |
|||
} |
|||
return re; |
|||
}, |
|||
|
|||
/** |
|||
* 计算两个经纬度点之间的距离 |
|||
* @param {Number} lng1 第一个点的经度 |
|||
* @param {Number} lat1 第一个点的纬度 |
|||
* @param {Number} lng2 第二个点的经度 |
|||
* @param {Number} lat2 第二个点的纬度 |
|||
*/ |
|||
calcDistance(lng1, lat1, lng2, lat2) { |
|||
var radLat1 = lat1 * Math.PI / 180.0; |
|||
var radLat2 = lat2 * Math.PI / 180.0; |
|||
var a = radLat1 - radLat2; |
|||
var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0; |
|||
var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + |
|||
Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); |
|||
s = s * 6378.137; // EARTH_RADIUS;
|
|||
s = Math.round(s * 10000) / 10000; |
|||
return s; |
|||
}, |
|||
|
|||
/** |
|||
* 获取数组最小值的下标 |
|||
* @param {Array} arr 数组 |
|||
*/ |
|||
getArrayMixValueIndex(arrar) { |
|||
let min = arrar[0]; |
|||
let index = 0; |
|||
for (let i = 0; i < arrar.length; i++) { |
|||
if (min > arrar[i]) { |
|||
min = arrar[i]; |
|||
index = i; |
|||
} |
|||
} |
|||
return index; |
|||
} |
|||
} |
@ -0,0 +1,170 @@ |
|||
import { |
|||
isEmpty |
|||
} from './TextUtils.js' |
|||
|
|||
const wxAuthLogin = () => { |
|||
// 判断是否已经登陆
|
|||
if (getApp().globalData.isLogin) { |
|||
return new Promise((resolve, reject) => { |
|||
resolve(getApp().globalData.sysUserSid) |
|||
}) |
|||
} |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
// 授权登录
|
|||
wx.login({ |
|||
success: function(res) { |
|||
if (res.code) { |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http网络请求info', { |
|||
"wxCode": res.code |
|||
}) |
|||
} |
|||
uni.showLoading({ |
|||
title: '加载中...', |
|||
mask: true |
|||
}); |
|||
|
|||
uni.request({ |
|||
// 组装请求地址
|
|||
url: getApp().globalData.wxAuthLoginURL, |
|||
// 请求方式 GET POST
|
|||
method: "GET", |
|||
header: { |
|||
// 传参方式
|
|||
'content-type': "application/x-www-form-urlencoded" |
|||
}, |
|||
// 具体参数
|
|||
data: { |
|||
"wxCode": res.code |
|||
}, |
|||
|
|||
success: res => { |
|||
|
|||
// 关闭显示框
|
|||
uni.hideLoading(); |
|||
|
|||
console.log(res) |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http网络路径', getApp().globalData.wxAuthLoginURL) |
|||
console.log('Http网络请求结果', JSON.parse(JSON.stringify( |
|||
res.data))) |
|||
} |
|||
|
|||
|
|||
if (res.statusCode == 200) { |
|||
// 下面是接口返回的数据
|
|||
if (!res.data.success) { |
|||
if ("A01001" == res.data.code || "A01002" == res.data.code ) { |
|||
// 绑定手机号
|
|||
uni.navigateTo({ |
|||
url: '../index/BindPhone?sysUserWxAuthSid=' + res.data.data |
|||
}) |
|||
// reject("绑定手机号")
|
|||
} |
|||
else { |
|||
// 错误提示
|
|||
if (isEmpty(res.data)) { |
|||
// 未成功获取到服务器返回的json
|
|||
uni.showToast({ |
|||
// 不能超过7个字
|
|||
title: "服务器响应为空", |
|||
icon: "error" |
|||
}) |
|||
reject("服务器响应为空") |
|||
} else { |
|||
|
|||
let errorMsg = res.data.msg |
|||
|
|||
if (isEmpty(errorMsg)) { |
|||
errorMsg = url+"服务器未返回错误信息" |
|||
} |
|||
|
|||
uni.showToast({ |
|||
title: errorMsg, |
|||
// 保证文字长度
|
|||
icon: "none", |
|||
duration: 3000 |
|||
}) |
|||
} |
|||
|
|||
reject(errorMsg) |
|||
} |
|||
return |
|||
} |
|||
|
|||
// 保存
|
|||
uni.setStorageSync("sysUserSid", res.data.data.sysUserSid); |
|||
uni.setStorageSync("isLogin", res.data.data.isLogin); |
|||
getApp().globalData.isLogin = res.data.data.isLogin |
|||
getApp().globalData.sysUserSid = res.data.data.sysUserSid |
|||
getApp().globalData.token = res.data.data.token |
|||
|
|||
// 直接返回Response
|
|||
resolve(getApp().globalData.sysUserSid) |
|||
|
|||
|
|||
} else { |
|||
|
|||
uni.showToast({ |
|||
title: res.statusCode + ":" + res |
|||
.errMsg, |
|||
// 保证文字长度
|
|||
icon: "none", |
|||
duration: 3000 |
|||
}) |
|||
|
|||
reject(res.errMsg) |
|||
} |
|||
|
|||
}, |
|||
fail: (err) => { |
|||
// 关闭显示框
|
|||
uni.hideLoading(); |
|||
|
|||
if (getApp().globalData.isDebug) { |
|||
console.log("Http网络请求fail", err) |
|||
} |
|||
|
|||
|
|||
uni.showToast({ |
|||
title: '请检查网络', |
|||
icon: 'error' |
|||
}) |
|||
|
|||
reject(err) |
|||
|
|||
}, |
|||
complete: () => { |
|||
|
|||
} |
|||
}); |
|||
|
|||
|
|||
} else { |
|||
|
|||
uni.showToast({ |
|||
title: "授权登录获取code失败:" + res.errMsg, |
|||
icon: 'none' |
|||
}) |
|||
|
|||
reject(res.errMsg) |
|||
} |
|||
}, |
|||
fail: function(res) { |
|||
uni.showToast({ |
|||
title: "授权登录失败:" + res.errMsg, |
|||
icon: 'none' |
|||
}) |
|||
|
|||
reject(res.errMsg) |
|||
} |
|||
}); |
|||
|
|||
}) |
|||
|
|||
} |
|||
|
|||
export default wxAuthLogin |
@ -0,0 +1,73 @@ |
|||
// 静默登录
|
|||
import { |
|||
isEmpty |
|||
} from './TextUtils.js' |
|||
const wxSilentLogin = () => { |
|||
return new Promise((resolve, reject) => { |
|||
wx.login({ |
|||
success: function(res) { |
|||
if (res.code) { |
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http网络请求信息', { |
|||
"wxCode": res.code |
|||
}) |
|||
} |
|||
uni.request({ |
|||
// 组装请求地址
|
|||
url: getApp().globalData.wxSilentLoginURL, |
|||
// 请求方式 GET POST
|
|||
method: "GET", |
|||
header: { |
|||
// 传参方式
|
|||
'content-type': "application/x-www-form-urlencoded" |
|||
}, |
|||
// 具体参数
|
|||
data: { |
|||
"wxCode": res.code |
|||
}, |
|||
success: res => { |
|||
console.log(res) |
|||
if (getApp().globalData.isDebug) { |
|||
console.log('Http网络路径', getApp().globalData.wxSilentLoginURL) |
|||
} |
|||
if (res.statusCode == 200) { |
|||
if (!res.data.success) { |
|||
return |
|||
} |
|||
else |
|||
{ |
|||
uni.setStorageSync("sysUserSid", res.data.data.sysUserSid); |
|||
uni.setStorageSync("token", res.data.data.token); |
|||
uni.setStorageSync("isLogin", res.data.data.isLogin); |
|||
|
|||
getApp().globalData.isLogin = res.data.data.isLogin |
|||
getApp().globalData.sysUserSid = res.data.data.sysUserSid |
|||
resolve(res.data.data.sysUserSid) |
|||
} |
|||
}else |
|||
{ |
|||
getApp().globalData.isLogin = false |
|||
} |
|||
}, |
|||
fail: (err) => { |
|||
if (getApp().globalData.isDebug) { |
|||
console.log("Http网络请求fail", err) |
|||
} |
|||
}, |
|||
complete: () => { |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
fail: function(res) { |
|||
uni.showToast({ |
|||
title: "静默登录失败:" + res.errMsg, |
|||
icon: 'none' |
|||
}) |
|||
|
|||
reject(res.errMsg) |
|||
} |
|||
}); |
|||
}) |
|||
} |
|||
export default wxSilentLogin |
@ -0,0 +1,76 @@ |
|||
<template> |
|||
<view style="display: flex;flex-direction: row;align-items: center;height:100rpx;"> |
|||
|
|||
<input v-if="(inputType=='text')" type="text" |
|||
style="margin-right: 30rpx;flex: 1;padding-left: 36rpx;font-size: 30rpx;" :placeholder="hint" |
|||
@input="textChange" :value="value" :maxlength="maxlength"></input> |
|||
<input v-if="(inputType=='number')" type="number" |
|||
style="margin-right: 30rpx;flex: 1;padding-left: 36rpx;font-size: 30rpx;" :placeholder="hint" |
|||
@input="textChange" :value="value" :maxlength="maxlength"></input> |
|||
<input v-if="(inputType=='idcard')" type="idcard" |
|||
style="margin-right: 30rpx;flex: 1;padding-left: 36rpx;font-size: 30rpx;" :placeholder="hint" |
|||
@input="textChange" :value="value" :maxlength="maxlength"></input> |
|||
<input v-if="(inputType=='digit')" type="digit" |
|||
style="margin-right: 30rpx;flex: 1;padding-left: 36rpx;font-size: 30rpx;" :placeholder="hint" |
|||
@input="textChange" :value="value" :maxlength="maxlength"></input> |
|||
<image v-if="isShowDelete" style="width: 50rpx;height: 50rpx;margin-right: 20rpx;flex-shrink: 0;" |
|||
src="../../static/img/public/shanchu1.png" mode="aspectFill" @click="clickRight()"> |
|||
</image> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
hint: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
text: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
inputType: { |
|||
type: String, |
|||
default: "text" |
|||
}, |
|||
maxlength: { |
|||
type: Number, |
|||
default: 99999999 |
|||
}, |
|||
|
|||
}, |
|||
watch: { |
|||
text: { |
|||
handler(t, oldT) { |
|||
this.value = t |
|||
this.$emit("onTextChange", this.value) |
|||
this.isShowDelete = !this.IsEmpty(this.value) |
|||
}, |
|||
immediate: true, |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
isShowDelete: false, |
|||
value: "" |
|||
}; |
|||
}, |
|||
methods: { |
|||
textChange(e) { |
|||
this.isShowDelete = !this.IsEmpty(e.detail.value) |
|||
this.$emit("onTextChange", e.detail.value) |
|||
this.value = e.detail.value |
|||
}, |
|||
clickRight() { |
|||
this.value = "" |
|||
this.$emit("onTextChange", this.value) |
|||
this.isShowDelete = !this.IsEmpty(this.value) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
@ -0,0 +1,51 @@ |
|||
<template> |
|||
<view |
|||
style="display: flex;flex-direction: row;box-sizing: border-box;width: 100%;align-items: center;height: 100rpx;padding: 0rpx 15rpx; background-color: #FFFFFF;"> |
|||
|
|||
<image style="width: 45rpx;height: 45rpx;" src="../../static/custom-icon/line.png" mode="aspectFill"> |
|||
</image> |
|||
<text style="flex-shrink: 0;font-size: 30rpx; color: #101010;">{{leftText}}</text> |
|||
<text |
|||
style="flex: 1;padding-left: 30rpx;padding-right: 30rpx;color: #898989 ;font-size: 26rpx;text-align: center;">{{middleText}}</text> |
|||
<text v-if="isShowRight" style="flex-shrink: 0;font-size: 26rpx;color: #080808;" |
|||
@click="clickRight()">{{rightText}}</text> |
|||
|
|||
<image v-if="isShowRight" style="width: 30rpx;height: 30rpx;margin-right: 20rpx;" |
|||
src="../../static/img/public/more.png" mode="aspectFill" @click="clickRight()"> |
|||
</image> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
leftText: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
middleText: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
rightText: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
isShowRight: { |
|||
type: Boolean, |
|||
default: true |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
clickRight() { |
|||
this.$emit("rightClick", this.leftText) |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
@ -0,0 +1,585 @@ |
|||
<template> |
|||
|
|||
<view :style="{'background-color':pageBg}"> |
|||
<view v-if="!hideTop" :style="{'height':totalHeightPx,'background-color': barColor,'box-sizing': 'border-box','width':'100%', |
|||
'z-index':'9900','position':'fixed','top':'--window-top','left':'0'}"> |
|||
<view :style="{'height':statusBarHeightPx}"> |
|||
</view> |
|||
<view |
|||
:style="{'height':navHeightPx,'width':navWidthPx,'display': 'flex','flex-direction':'row','box-sizing': 'border-box','justify-content': 'space-between'}"> |
|||
<view |
|||
:style="{'height':navHeightPx,'display': 'flex','flex-direction':'row','box-sizing': 'border-box'}"> |
|||
<image :style="{'height':navHeightPx,'width':iconPx,'flex-shrink':0}" mode="aspectFit" |
|||
:src="backIcon" @click="clickBack"></image> |
|||
<text class="titleStyle" |
|||
:style="{'height':navHeightPx,'line-height':navHeightPx,'font-size':'33rpx','color':'#FFFFFF'}">{{text}}</text> |
|||
</view> |
|||
|
|||
<view |
|||
:style="{'height':navHeightPx,'display': 'flex','flex-direction':'row','box-sizing': 'border-box'}"> |
|||
<view v-if="useTitleLeftBtn!=0" |
|||
:style="{'height':navHeightPx,'display': 'flex','flex-direction':'row','box-sizing': 'border-box'}" |
|||
@click="clickLeftBtn"> |
|||
<image v-if="useTitleLeftBtn == 2 && dropLeftList.length == 0" |
|||
:style="{'height':navHeightPx,'width':navHeightPx}" mode="aspectFit" |
|||
:src="titleLeftBtnSource"></image> |
|||
<text v-if="useTitleLeftBtn == 1 && dropLeftList.length == 0" |
|||
:style="{'height':navHeightPx,'line-height':navHeightPx,'font-size':'28rpx','color':'#FFFFFF','padding-left':'20rpx','padding-right':'20rpx'}">{{titleLeftBtnSource}}</text> |
|||
<drop-item ref="dropview" :candidates="dropLeftList" :btnType="useTitleLeftBtn" |
|||
:btnSource="titleLeftBtnSource" @onselect="selectDrop"></drop-item> |
|||
</view> |
|||
<view v-if="useTitleRightBtn!=0" |
|||
:style="{'height':navHeightPx,'display': 'flex','flex-direction':'row','box-sizing': 'border-box'}" |
|||
@click="clickRightBtn"> |
|||
<image v-if="useTitleRightBtn==2 && dropRightList.length == 0" |
|||
:style="{'height':navHeightPx,'width':navHeightPx}" mode="aspectFit" |
|||
:src="titleRightBtnSource"></image> |
|||
<text v-if="useTitleRightBtn==1 && dropRightList.length == 0" |
|||
:style="{'height':navHeightPx,'line-height':navHeightPx,'font-size':'28rpx','color':'#FFFFFF','padding-left':'20rpx','padding-right':'20rpx'}">{{titleRightBtnSource}}</text> |
|||
<drop-item ref="dropview" :candidates="dropRightList" :btnType="useTitleRightBtn" |
|||
:btnSource="titleRightBtnSource" @onselect="selectDrop"></drop-item> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="mescroll-body" |
|||
:style="{'minHeight':minHeight, 'padding-top': totalHeightPx, 'padding-bottom': padBottom, 'padding-bottom': padBottomConstant, 'padding-bottom': padBottomEnv }" |
|||
@touchstart="touchstartEvent" @touchmove="touchmoveEvent" @touchend="touchendEvent" |
|||
@touchcancel="touchendEvent"> |
|||
<view class="mescroll-body-content" :style="{ transform: translateY, transition: transition }"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> --> |
|||
<view v-if="useDownScroll" class="mescroll-downwarp" |
|||
:style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<image class="downwarp-slogan" src="../../static/img/public/mescroll-slogan.png" |
|||
mode="widthFix" /> |
|||
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view> |
|||
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view> |
|||
</view> |
|||
</view> |
|||
|
|||
<sl-filter id="header" ref="slLilter" v-if="menuList.length!=0" :color="titleColor" :themeColor="themeColor" |
|||
:menuList.sync="menuList" @result="result" ></sl-filter> |
|||
|
|||
<!-- 列表内容 --> |
|||
<slot></slot> |
|||
|
|||
<!-- 空布局 --> |
|||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"> |
|||
</mescroll-empty> |
|||
|
|||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)--> |
|||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view v-if="useUpScroll && !isDownLoading" class="mescroll-upwarp" |
|||
:style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-if="upLoadType===1"> |
|||
<view class="upwarp-progress mescroll-rotate" |
|||
:style="{'border-color':mescroll.optUp.textColor}"></view> |
|||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
import MeScroll from '../mescroll-uni/mescroll-uni.js'; |
|||
import MescrollEmpty from '../mescroll-uni/components/mescroll-empty.vue'; |
|||
import MescrollTop from '../mescroll-uni/components/mescroll-top.vue'; |
|||
import GlobalOption from '../mescroll-uni/mescroll-uni-option.js'; |
|||
import RefreshOption from './refresh-option.js'; |
|||
import slFilter from '@/components/sl-filter/sl-filter.vue'; |
|||
|
|||
export default { |
|||
components: { |
|||
MescrollEmpty, |
|||
MescrollTop, |
|||
slFilter |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: null, // mescroll实例 |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downLoadType: 4, // 下拉刷新状态 (inOffset:1, outOffset:2, showLoading:3, endDownScroll:4) |
|||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
statusBarH: 0, // 状态栏高度 |
|||
statusBarHeight: "", |
|||
navHeight: "", |
|||
navWidth: "", |
|||
totalHeight: "", |
|||
statusBarHeightPx: "", |
|||
navHeightPx: "", |
|||
navWidthPx: "", |
|||
totalHeightPx: "", |
|||
bg: "#2fa1f0", |
|||
totalHeightUpx: "", |
|||
backIcon: "", |
|||
iconPx: "", |
|||
LeftPos: '0px', |
|||
themeColor: '#000000', |
|||
titleColor: '#fd6d2a', |
|||
hideTop: false |
|||
}; |
|||
}, |
|||
props: { |
|||
down: Object, // 下拉刷新的参数配置 |
|||
up: Object, // 上拉加载的参数配置 |
|||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
topbar: Boolean, // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可自动加上状态栏高度的偏移量) |
|||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
text: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
pageBg: { |
|||
type: String, |
|||
default: "#FFFFFF" |
|||
}, |
|||
barColor: { |
|||
type: String, |
|||
default: "#2fa1f0" |
|||
}, |
|||
hasBack: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
isShareIn: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
isInterceptBack: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
useUpScroll: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
useDownScroll: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
useTitleLeftBtn: { |
|||
type: String, |
|||
// 0不使用 1使用文字 2使用图片 |
|||
default: "0" |
|||
}, |
|||
useTitleRightBtn: { |
|||
type: String, |
|||
// 0不使用 1使用文字 2使用图片 |
|||
default: "0" |
|||
}, |
|||
titleLeftBtnSource: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
titleRightBtnSource: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
dropLeftList: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
dropRightList: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
dropShow: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
menuList: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
minHeight() { |
|||
return this.toPx(this.height || '100%') + 'px' |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) + (this.topbar ? this.statusBarH : 0); |
|||
}, |
|||
padTop() { |
|||
return this.numTop + 'px'; |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom); |
|||
}, |
|||
padBottom() { |
|||
return this.numBottom + 'px'; |
|||
}, |
|||
padBottomConstant() { |
|||
return this.isSafearea ? 'calc(' + this.padBottom + ' + constant(safe-area-inset-bottom))' : this |
|||
.padBottom; |
|||
}, |
|||
padBottomEnv() { |
|||
return this.isSafearea ? 'calc(' + this.padBottom + ' + env(safe-area-inset-bottom))' : this.padBottom; |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
isDownReset() { |
|||
return this.downLoadType === 3 || this.downLoadType === 4; |
|||
}, |
|||
// 过渡 |
|||
transition() { |
|||
return this.isDownReset ? 'transform 300ms' : this.downTransition; |
|||
}, |
|||
translateY() { |
|||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : |
|||
''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading() { |
|||
return this.downLoadType === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate() { |
|||
return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)' |
|||
} |
|||
}, |
|||
methods: { |
|||
refreshTitle(obj) { |
|||
this.$refs.slLilter.setTitle(obj) |
|||
}, |
|||
reset(){ |
|||
this.$refs.slLilter.resetSelectToDefault(function(e) { |
|||
callback(e); |
|||
}); |
|||
}, |
|||
result(val) { |
|||
console.log("val:", JSON.stringify(val)); |
|||
this.$emit('result', JSON.stringify(val)) |
|||
}, |
|||
selectDrop(index, data) { |
|||
let left = this.arrSame(data, this.dropLeftList); |
|||
this.$emit("drop", index, left, (left ? this.dropLeftList[index] : this.dropRightList[index])) |
|||
}, |
|||
clickLeftBtn() { |
|||
this.$emit("leftBtn", "左侧按钮") |
|||
}, |
|||
clickRightBtn() { |
|||
this.$emit("rightBtn", "右侧按钮") |
|||
}, |
|||
clickBack() { |
|||
if (this.hasBack) { |
|||
|
|||
if (getCurrentPages().length > 1) { |
|||
|
|||
if (this.isInterceptBack) { |
|||
this.$emit("backClick") |
|||
} else { |
|||
this.Back() |
|||
} |
|||
|
|||
} else { |
|||
if (RefreshOption.homePageIsTabBar) { |
|||
uni.switchTab({ |
|||
url: RefreshOption.homePagePath |
|||
}) |
|||
} else { |
|||
uni.navigateTo({ |
|||
url: RefreshOption.homePagePath |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
//number,rpx,upx,px,% --> px的数值 |
|||
toPx(num) { |
|||
if (typeof num === 'string') { |
|||
if (num.indexOf('px') !== -1) { |
|||
if (num.indexOf('rpx') !== -1) { |
|||
// "10rpx" |
|||
num = num.replace('rpx', ''); |
|||
} else if (num.indexOf('upx') !== -1) { |
|||
// "10upx" |
|||
num = num.replace('upx', ''); |
|||
} else { |
|||
// "10px" |
|||
return Number(num.replace('px', '')); |
|||
} |
|||
} else if (num.indexOf('%') !== -1) { |
|||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|||
let rate = Number(num.replace('%', '')) / 100; |
|||
return this.windowHeight * rate; |
|||
} |
|||
} |
|||
return num ? uni.upx2px(Number(num)) : 0; |
|||
}, |
|||
//注册列表touchstart事件,用于下拉刷新 |
|||
touchstartEvent(e) { |
|||
if (this.$props.useDownScroll) { |
|||
this.mescroll.touchstartEvent(e); |
|||
} |
|||
}, |
|||
//注册列表touchmove事件,用于下拉刷新 |
|||
touchmoveEvent(e) { |
|||
if (this.$props.useDownScroll) { |
|||
this.mescroll.touchmoveEvent(e); |
|||
} |
|||
}, |
|||
//注册列表touchend事件,用于下拉刷新 |
|||
touchendEvent(e) { |
|||
if (this.$props.useDownScroll) { |
|||
this.mescroll.touchendEvent(e); |
|||
} |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
emptyClick() { |
|||
this.$emit('emptyclick', this.mescroll); |
|||
}, |
|||
// 点击回到顶部的按钮回调 |
|||
toTopClick() { |
|||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|||
}, |
|||
refreshFinished(currentPageListSize) { |
|||
|
|||
if (currentPageListSize == undefined || currentPageListSize == null || currentPageListSize === "") { |
|||
this.mescroll.endSuccess(); |
|||
} else { |
|||
this.mescroll.endSuccess(currentPageListSize); |
|||
} |
|||
}, |
|||
refreshError() { |
|||
this.mescroll.endErr(); |
|||
}, |
|||
resetPageOne() { |
|||
this.mescroll.resetUpScroll() |
|||
// 防止刷新的时候 显示不是第一个 |
|||
this.toTopClick() |
|||
}, |
|||
closeDrop() { |
|||
// 关闭框 |
|||
if (this.$refs.dropview != undefined) |
|||
this.$refs.dropview.closeDropItem() |
|||
}, |
|||
/** |
|||
* 验证两个object 是否相同 |
|||
* @param {Object} obj [需要进行验证的数据1] |
|||
* @param {Object} newObj [需要进行验证的数据2] |
|||
*/ |
|||
objSame(obj, newObj) { |
|||
let bol = true; |
|||
if (Object.keys(obj).length != Object.keys(newObj).length) { |
|||
return false; |
|||
} |
|||
for (let key in obj) { |
|||
if (obj[key] instanceof Object) { |
|||
bol = this.objSame(obj[key], newObj[key]); |
|||
if (!bol) { |
|||
break; |
|||
} |
|||
} else if (obj[key] instanceof Array) { |
|||
bol = arrSame(obj[key], newObj[key]) |
|||
if (!bol) { |
|||
break; |
|||
} |
|||
} else if (obj[key] != newObj[key]) { |
|||
bol = false; |
|||
break; |
|||
} |
|||
} |
|||
return bol |
|||
}, |
|||
|
|||
/** |
|||
* 验证两个数组是否相同 |
|||
* @param {Array} arr [需要进行验证的数据1] |
|||
* @param {Array} newArr [需要进行验证的数据2] |
|||
*/ |
|||
arrSame(arr, newArr) { |
|||
let bol = true; |
|||
if (arr.length != newArr.length) { |
|||
return false; |
|||
} |
|||
for (let i = 0, n = arr.length; i < n; i++) { |
|||
if (arr[i] instanceof Array) { |
|||
bol = arrSame(arr[i], newArr[i]) |
|||
if (!bol) { |
|||
break; |
|||
} |
|||
} else if (arr[i] instanceof Object) { |
|||
bol = this.objSame(arr[i], newArr[i]) |
|||
if (!bol) { |
|||
break; |
|||
} |
|||
} else if (arr[i] != newArr[i]) { |
|||
bol = false; |
|||
break; |
|||
} |
|||
} |
|||
return bol; |
|||
} |
|||
}, |
|||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|||
created() { |
|||
|
|||
this.navWidth = getApp().globalData.navWidth |
|||
this.navHeight = getApp().globalData.navHeight |
|||
this.totalHeight = getApp().globalData.totalHeight |
|||
this.statusBarHeight = getApp().globalData.statusBarHeight |
|||
this.navWidthPx = getApp().globalData.navWidthPx |
|||
this.navHeightPx = getApp().globalData.navHeightPx |
|||
this.totalHeightPx = getApp().globalData.totalHeightPx |
|||
this.statusBarHeightPx = getApp().globalData.statusBarHeightPx |
|||
this.totalHeightUpx = getApp().globalData.totalHeightUpx |
|||
|
|||
if (this.$props.hasBack) { |
|||
this.backIcon = getCurrentPages().length > 1 ? RefreshOption.backIcon : RefreshOption.homeIcon |
|||
this.iconPx = this.navHeightPx |
|||
} else { |
|||
this.backIcon = '../../static/custom-icon/no-back.png' |
|||
this.iconPx = this.navHeight / 8 * 3 + 'px' |
|||
} |
|||
|
|||
|
|||
let vm = this; |
|||
|
|||
let diyOption = { |
|||
// 下拉刷新的配置 |
|||
down: { |
|||
inOffset(mescroll) { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset(mescroll) { |
|||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
onMoving(mescroll, rate, downHight) { |
|||
// 下拉过程中的回调,滑动过程一直在执行; |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
showLoading(mescroll, downHight) { |
|||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
endDownScroll(mescroll) { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.mescroll.resetUpScroll() |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll() { |
|||
vm.upLoadType = 0; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('refresh', mescroll); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
MeScroll.extend(diyOption, GlobalOption); // 混入全局的配置 |
|||
let myOption = JSON.parse( |
|||
JSON.stringify({ |
|||
down: vm.down, |
|||
up: vm.up |
|||
}) |
|||
); // 深拷贝,避免对props的影响 |
|||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置 |
|||
|
|||
// 初始化MeScroll对象 |
|||
vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域 |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if (sys.statusBarHeight) vm.statusBarH = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
// mescroll-body在Android小程序下拉会卡顿,无法像mescroll-uni那样通过设置"disableScroll":true解决,只能用动画过渡缓解 |
|||
// #ifdef MP |
|||
if (sys.platform == "android") vm.downTransition = 'transform 200ms' |
|||
// #endif |
|||
|
|||
// 因为使用的是page的scroll,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
uni.pageScrollTo({ |
|||
scrollTop: y, |
|||
duration: t |
|||
}) |
|||
}); |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (sys.platform == "ios") { |
|||
vm.isSafearea = vm.safearea; |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
} else { |
|||
vm.isSafearea = false |
|||
vm.mescroll.optUp.toTop.safearea = false |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "@/components/mescroll-uni/mescroll-body.css"; |
|||
@import "@/components/mescroll-uni/components/mescroll-down.css"; |
|||
@import "@/components/mescroll-uni/components/mescroll-up.css"; |
|||
@import "@/components/RefreshView/mescroll-down.css"; |
|||
|
|||
.titleStyle { |
|||
display: -webkit-box; |
|||
-webkit-box-orient: vertical; |
|||
-webkit-line-clamp: 1; |
|||
overflow: hidden; |
|||
} |
|||
</style> |
@ -0,0 +1,65 @@ |
|||
/*下拉刷新--标语*/ |
|||
.mescroll-downwarp .downwarp-slogan { |
|||
display: block; |
|||
width: 420rpx; |
|||
height: 168rpx; |
|||
margin: auto; |
|||
} |
|||
|
|||
/*下拉刷新--向下进度动画*/ |
|||
.mescroll-downwarp .downwarp-progress { |
|||
display: inline-block; |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
border: none; |
|||
margin: auto; |
|||
background-size: contain; |
|||
background-repeat: no-repeat; |
|||
background-position: center; |
|||
background-image: url(../../static/img/public/mescroll-progress.png); |
|||
transition: all 300ms; |
|||
} |
|||
|
|||
/*下拉刷新--进度条*/ |
|||
.mescroll-downwarp .downwarp-loading { |
|||
display: inline-block; |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
border-radius: 50%; |
|||
border: 2rpx solid #FF8095; |
|||
border-bottom-color: transparent; |
|||
} |
|||
|
|||
/*下拉刷新--吉祥物*/ |
|||
.mescroll-downwarp .downwarp-mascot { |
|||
position: absolute; |
|||
right: 16rpx; |
|||
bottom: 0; |
|||
width: 100rpx; |
|||
height: 100rpx; |
|||
background-size: contain; |
|||
background-repeat: no-repeat; |
|||
animation: animMascot .6s steps(1, end) infinite; |
|||
} |
|||
|
|||
@keyframes animMascot { |
|||
0% { |
|||
background-image: url(http://www.mescroll.com/img/beibei/mescroll-bb1.png) |
|||
} |
|||
|
|||
25% { |
|||
background-image: url(http://www.mescroll.com/img/beibei/mescroll-bb2.png) |
|||
} |
|||
|
|||
50% { |
|||
background-image: url(http://www.mescroll.com/img/beibei/mescroll-bb3.png) |
|||
} |
|||
|
|||
75% { |
|||
background-image: url(http://www.mescroll.com/img/beibei/mescroll-bb4.png) |
|||
} |
|||
|
|||
100% { |
|||
background-image: url(http://www.mescroll.com/img/beibei/mescroll-bb1.png) |
|||
} |
|||
} |
@ -0,0 +1,79 @@ |
|||
***********************************下拉框************************************* |
|||
|
|||
|
|||
1.dropLeftList: type: Array, default: [] |
|||
// 左边下拉内容名称list |
|||
|
|||
2.dropRightList: type: Array, default: [] |
|||
// 右边下拉内容名称list |
|||
|
|||
3.@drop :drop(id,isLeft,selectData) |
|||
// 监听用户选择的条目id,是否是左侧按钮的下拉,选中的数据 |
|||
|
|||
4.closeDrop() |
|||
// 关闭下拉框 |
|||
|
|||
|
|||
***********************************导航栏************************************* |
|||
|
|||
1.hasBack: type: Boolean,default: true |
|||
// 是否有返回键 |
|||
|
|||
2.useDownScroll: type: Boolean,default: true |
|||
// 是否支持下拉刷新 |
|||
|
|||
3.useUpScroll: ype: Boolean,default: true |
|||
// 是否支持上拉加载 |
|||
// 使用上拉加载时 需引入 |
|||
// import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js"; |
|||
// mixins: [MescrollMixin] |
|||
|
|||
4.text: type: String,default: "" |
|||
// 导航栏标题 |
|||
|
|||
5.useTitleLeftBtn: type: String,default: "0" |
|||
// 导航栏最右边的两个按钮其中左边的按钮 0不使用 1使用文字 2使用图片 |
|||
|
|||
6.useTitleRightBtn: type: String,default: "0" |
|||
// 导航栏最右边的两个按钮其中右边的按钮 0不使用 1使用文字 2使用图片 |
|||
|
|||
7.titleLeftBtnSource: type: String,default: "" |
|||
// 导航栏最右边的两个按钮其中左边的按钮文字或图片资源 |
|||
|
|||
8.titleRightBtnSource: type: String,default: "" |
|||
// 导航栏最右边的两个按钮其中右边的按钮文字或图片资源 |
|||
|
|||
9.@refresh: refresh(page) |
|||
// 下拉或上拉的监听 参数为当前请求的页数 |
|||
|
|||
10.@leftBtn: |
|||
// 导航栏最右边的两个按钮其中左边的按钮点击的监听 |
|||
|
|||
11.@rightBtn: |
|||
// 导航栏最右边的两个按钮其中右边的按钮点击的监听 |
|||
|
|||
12.方法: refreshFinished() |
|||
// 网络请求成功后关闭下拉刷新动画与上拉加载动画 |
|||
// 如果为分页请求 参数为当前接口返回list数据的长度(必须) |
|||
// 否则参数可不传 |
|||
|
|||
13.方法: refreshError() |
|||
// 网络请求失败后关闭下拉刷新动画与上拉加载动画 |
|||
|
|||
14.方法: resetPageOne() |
|||
// 调用此方法可重置为第一页(比如说退赛需要重新刷新列表,调用此方法会响应 @refresh 监听) |
|||
|
|||
15.pageBg: type: String,default: "" |
|||
// 页面背景色 |
|||
|
|||
16.barColor: type: String,default: "" |
|||
// 顶部导航栏颜色 |
|||
|
|||
17.isShareIn: type: Boolean,default: true |
|||
// 是否是分享进入(分享进入返回键会显示主页) |
|||
// h5分享时需加入 isShareIn=true 小程序分享时onShareAppMessage() 也需加入isShareIn=true |
|||
|
|||
18.isInterceptBack: type: Boolean,default: false |
|||
// 是否拦截返回键 |
|||
// :isInterceptBack="true" @backClick = "方法名" |
|||
// 点击返回时会回调到上面设置方法名的那个方法里 |
@ -0,0 +1,19 @@ |
|||
// 全局配置
|
|||
const RefreshOption = { |
|||
// 背景色
|
|||
pageBg: '#FFFFFF', |
|||
// 导航栏颜色
|
|||
barColor: '#2fa1f0', |
|||
// 是否有返回键(会自动判断是否可返回)
|
|||
hasBack: true, |
|||
// 首页
|
|||
homePagePath: '../../pages/home/FindFragment', |
|||
// 首页是否是切换的页面
|
|||
homePageIsTabBar: true, |
|||
// 返回键
|
|||
backIcon: '../../static/img/public/back.png', |
|||
// 主页
|
|||
homeIcon: '../../static/img/public/home.png' |
|||
} |
|||
|
|||
export default RefreshOption |
@ -0,0 +1,275 @@ |
|||
<template> |
|||
|
|||
<view class="page-body" :style="{height:'calc(100vh - '+removeHeight+')'}"> |
|||
|
|||
<view class='wrapper'> |
|||
<view class="editor-wrapper"> |
|||
<editor id="editor" class="ql-container" :placeholder="placeholder" showImgSize showImgToolbar |
|||
showImgResize @statuschange="onStatusChange" :read-only="readOnly" @ready="onEditorReady" |
|||
@input="getEditorContent"> |
|||
</editor> |
|||
</view> |
|||
|
|||
<view class='toolbar' @tap="format" style="height: 120px;overflow-y: auto;"> |
|||
|
|||
<view class="iconfont icon-undo" @tap="undo"></view> |
|||
<view class="iconfont icon-redo" @tap="redo"></view> |
|||
|
|||
<view :class="formats.bold ? 'ql-active' : ''" class="iconfont icon-zitijiacu" data-name="bold"> |
|||
</view> |
|||
<view :class="formats.italic ? 'ql-active' : ''" class="iconfont icon-zitixieti" data-name="italic"> |
|||
</view> |
|||
<view :class="formats.underline ? 'ql-active' : ''" class="iconfont icon-zitixiahuaxian" |
|||
data-name="underline"></view> |
|||
<view :class="formats.strike ? 'ql-active' : ''" class="iconfont icon-zitishanchuxian" |
|||
data-name="strike"></view> |
|||
|
|||
<view :class="formats.color === '#0000ff' ? 'ql-active' : ''" class="iconfont icon-text_color" |
|||
data-name="color" data-value="#0000ff"></view> |
|||
<view :class="formats.backgroundColor === '#00ff00' ? 'ql-active' : ''" |
|||
class="iconfont icon-fontbgcolor" data-name="backgroundColor" data-value="#00ff00"></view> |
|||
<view class="iconfont icon-charutupian" @tap="insertImage"></view> |
|||
|
|||
<view class="iconfont icon-shanchu" @tap="clear"></view> |
|||
<view :class="formats.align === 'left' ? 'ql-active' : ''" class="iconfont icon-zuoduiqi" |
|||
data-name="align" data-value="left"></view> |
|||
<view :class="formats.align === 'center' ? 'ql-active' : ''" class="iconfont icon-juzhongduiqi" |
|||
data-name="align" data-value="center"></view> |
|||
<view :class="formats.align === 'right' ? 'ql-active' : ''" class="iconfont icon-youduiqi" |
|||
data-name="align" data-value="right"></view> |
|||
<view :class="formats.align === 'justify' ? 'ql-active' : ''" class="iconfont icon-zuoyouduiqi" |
|||
data-name="align" data-value="justify"></view> |
|||
<view :class="formats.lineHeight ? 'ql-active' : ''" class="iconfont icon-line-height" |
|||
data-name="lineHeight" data-value="2"></view> |
|||
<view :class="formats.letterSpacing ? 'ql-active' : ''" class="iconfont icon-Character-Spacing" |
|||
data-name="letterSpacing" data-value="2em"></view> |
|||
<view :class="formats.marginTop ? 'ql-active' : ''" class="iconfont icon-722bianjiqi_duanqianju" |
|||
data-name="marginTop" data-value="20px"></view> |
|||
<view :class="formats.previewarginBottom ? 'ql-active' : ''" class="iconfont icon-723bianjiqi_duanhouju" |
|||
data-name="marginBottom" data-value="20px"></view> |
|||
<view :class="formats.fontSize === '24px' ? 'ql-active' : ''" class="iconfont icon-fontsize" |
|||
data-name="fontSize" data-value="24px"></view> |
|||
|
|||
|
|||
<view :class="formats.list === 'ordered' ? 'ql-active' : ''" class="iconfont icon-youxupailie" |
|||
data-name="list" data-value="ordered"></view> |
|||
<view :class="formats.list === 'bullet' ? 'ql-active' : ''" class="iconfont icon-wuxupailie" |
|||
data-name="list" data-value="bullet"></view> |
|||
|
|||
<view class="iconfont icon-outdent" data-name="indent" data-value="-1"></view> |
|||
<view class="iconfont icon-indent" data-name="indent" data-value="+1"></view> |
|||
<view class="iconfont icon-fengexian" @tap="insertDivider"></view> |
|||
|
|||
<view :class="formats.script === 'sub' ? 'ql-active' : ''" class="iconfont icon-zitixiabiao" |
|||
data-name="script" data-value="sub"></view> |
|||
<view :class="formats.script === 'super' ? 'ql-active' : ''" class="iconfont icon-zitishangbiao" |
|||
data-name="script" data-value="super"></view> |
|||
<view :class="formats.direction === 'rtl' ? 'ql-active' : ''" class="iconfont icon-direction-rtl" |
|||
data-name="direction" data-value="rtl"></view> |
|||
|
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
html: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
placeholder: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
removeHeight: { |
|||
type: String, |
|||
default: '0px' |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
readOnly: false, |
|||
formats: {}, |
|||
result: { |
|||
html: "", |
|||
text: "", |
|||
delta: {} |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
readOnlyChange() { |
|||
this.readOnly = !this.readOnly |
|||
}, |
|||
onEditorReady() { |
|||
|
|||
// #ifdef APP-PLUS |
|||
uni.createSelectorQuery().select('#editor').context((res) => { |
|||
this.editorCtx = res.context |
|||
|
|||
let _this = this |
|||
|
|||
let EContent = { |
|||
html: this.html |
|||
} |
|||
//设置富文本编辑器的内容 |
|||
this.editorCtx.setContents(EContent); |
|||
|
|||
this.editorCtx.getContents({ |
|||
complete: res => { |
|||
_this.result = res |
|||
} |
|||
}) |
|||
}).exec() |
|||
// #endif |
|||
|
|||
// #ifdef H5 || MP-WEIXIN |
|||
this.createSelectorQuery().select('#editor').context((res) => { |
|||
this.editorCtx = res.context |
|||
|
|||
let _this = this |
|||
|
|||
let EContent = { |
|||
html: this.html |
|||
} |
|||
//设置富文本编辑器的内容 |
|||
this.editorCtx.setContents(EContent); |
|||
|
|||
this.editorCtx.getContents({ |
|||
complete: res => { |
|||
_this.result = res |
|||
} |
|||
}) |
|||
}).exec() |
|||
// #endif |
|||
}, |
|||
undo() { |
|||
this.editorCtx.undo() |
|||
}, |
|||
redo() { |
|||
this.editorCtx.redo() |
|||
}, |
|||
format(e) { |
|||
let { |
|||
name, |
|||
value |
|||
} = e.target.dataset |
|||
if (!name) return |
|||
this.editorCtx.format(name, value) |
|||
|
|||
}, |
|||
onStatusChange(e) { |
|||
const formats = e.detail |
|||
this.formats = formats |
|||
}, |
|||
insertDivider() { |
|||
this.editorCtx.insertDivider({ |
|||
success: function() { |
|||
console.log('insert divider success') |
|||
} |
|||
}) |
|||
}, |
|||
clear() { |
|||
this.editorCtx.clear({ |
|||
success: function(res) { |
|||
console.log("clear success") |
|||
} |
|||
}) |
|||
}, |
|||
removeFormat() { |
|||
this.editorCtx.removeFormat() |
|||
}, |
|||
insertDate() { |
|||
const date = new Date() |
|||
const formatDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}` |
|||
this.editorCtx.insertText({ |
|||
text: formatDate |
|||
}) |
|||
}, |
|||
insertImage() { |
|||
let that = this |
|||
console.log('00000>>') |
|||
uni.chooseImage({ |
|||
count: 1, |
|||
success: (res) => { |
|||
console.log('1111>>'+res.tempFilePaths[0]) |
|||
that.$emit("insertPic", res.tempFilePaths[0]) |
|||
} |
|||
}) |
|||
}, |
|||
insertUrlImage(imageUrl) { |
|||
console.log('22222>>'+imageUrl) |
|||
this.editorCtx.insertImage({ |
|||
src: imageUrl, |
|||
alt: '图像', |
|||
success: function() { |
|||
console.log('insert image success') |
|||
} |
|||
}) |
|||
}, |
|||
getEditorContent(e) { |
|||
this.result = e.detail; |
|||
}, |
|||
getHtml() { |
|||
return this.result.html |
|||
}, |
|||
getText() { |
|||
return this.result.text |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "./editor-icon.css"; |
|||
|
|||
.page-body { |
|||
background-color: #4CD964; |
|||
|
|||
.wrapper { |
|||
height: 100%; |
|||
|
|||
background-color: #FFFFFF; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.editor-wrapper { |
|||
height: calc(100% - 120px); |
|||
background: #fff; |
|||
} |
|||
|
|||
.toolbar { |
|||
box-sizing: border-box; |
|||
border-bottom: 0; |
|||
background-color: #F1F1F1; |
|||
} |
|||
} |
|||
|
|||
|
|||
.iconfont { |
|||
display: inline-block; |
|||
padding: 8px 8px; |
|||
width: 24px; |
|||
height: 24px; |
|||
cursor: pointer; |
|||
font-size: 20px; |
|||
} |
|||
|
|||
.ql-container { |
|||
box-sizing: border-box; |
|||
padding: 12px 15px; |
|||
width: 100%; |
|||
min-height: 30vh; |
|||
height: 100%; |
|||
font-size: 16px; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.ql-active { |
|||
color: #06c; |
|||
} |
|||
</style> |
File diff suppressed because one or more lines are too long
@ -0,0 +1,30 @@ |
|||
## 参数 |
|||
| 参数 | 类型 | 解释 | 默认 | 必填 | |
|||
| ------------ | ------------ | ------------ | ------------ | ------------ | |
|||
| html | String | 默认html(模板内容) | "" | 否 | |
|||
| placeholder | String | 提示信息 | "" | 否 | |
|||
| removeHeight | String | 移除高度(默认占满屏幕高度) | "0px" | 否 | |
|||
|
|||
## 方法 |
|||
|
|||
| 方法名 | 解释 | 参数 | |
|||
| ------------ | ------------ | ------------ | |
|||
| getHtml | 获取富文本html | 无 | |
|||
| getText | 获取富文本text | 无 | |
|||
| insertUrlImage | 插入网络图片 | 图片地址 | |
|||
|
|||
## 事件 |
|||
|
|||
| 事件名 | 解释 | 参数 | |
|||
| ------------ | ------------ | ------------ | |
|||
| insertPic | 用户选取本地图片成功后回调 | 选择的本地图片地址 | |
|||
|
|||
## 支持平台 |
|||
|
|||
| app | 微信小程序 | |
|||
| ------------ | ------------ | |
|||
| 未测试 | 支持 | |
|||
|
|||
| h5-Safari | Android Browser | 微信浏览器(Android) |QQ浏览器(Android) | |
|||
| ------------ | ------------ | ------------ | | |
|||
| 未测试 | 支持 | 支持 | 支持 | |
@ -0,0 +1,108 @@ |
|||
<template> |
|||
<view :style="{'border-radius': '5rpx','height': '65rpx','width': '160rpx','text-align': 'center','line-height': '65rpx','font-size': '26rpx', |
|||
'background-color': background,'color': 'white'}" |
|||
@click="click"> |
|||
{{textDetail}} |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
'background': '#2fa1f0', |
|||
"isCanClick": true, |
|||
"textDetail": "获取验证码", |
|||
"countdown": 60, |
|||
'cookie': '' |
|||
}; |
|||
}, |
|||
methods: { |
|||
getCookie() { |
|||
return this.cookie; |
|||
}, |
|||
click() { |
|||
|
|||
if (!this.isCanClick) { |
|||
return |
|||
} |
|||
|
|||
if (this.phoneNum.length != 11 || !this.phoneNum.startsWith("1")) { |
|||
this.Toast("请输入合法的手机号") |
|||
} else { |
|||
|
|||
// 更改样式并且不可点击 |
|||
this.background = "gray" |
|||
|
|||
this.isCanClick = false |
|||
|
|||
let timeOut = setInterval(() => { |
|||
|
|||
if (this.countdown == 1) { |
|||
this.textDetail = "获取验证码" |
|||
this.countdown = 60 |
|||
clearInterval(timeOut) |
|||
this.background = "#2fa1f0" |
|||
|
|||
this.isCanClick = true |
|||
} else { |
|||
this.countdown = this.countdown - 1; |
|||
this.textDetail = this.countdown + "s" |
|||
} |
|||
}, 1000) |
|||
|
|||
let _this = this |
|||
|
|||
// if (this.sendByCookie != null) { |
|||
|
|||
// // 此方法会判断是否是小程序,小程序才执行cookie |
|||
// this.HttpCookie({ |
|||
// 'url': this.url, |
|||
// 'data': { |
|||
// "mobile": this.phoneNum |
|||
// }, |
|||
// cookie: this.sendByCookie, |
|||
// loading: true |
|||
// }).then((res) => { |
|||
// _this.cookie = res.data |
|||
// }, (err) => { |
|||
// clearInterval(timeOut) |
|||
// this.countdown = 60 |
|||
// // 失败重置状态 |
|||
// this.background = "#2fa1f0" |
|||
|
|||
// this.isCanClick = true |
|||
// this.textDetail = "获取验证码" |
|||
// }) |
|||
|
|||
// } else { |
|||
_this.HTTP({ |
|||
url: _this.url+"?mobile="+_this.phoneNum, |
|||
data: {}, |
|||
method: 'GET', |
|||
paramsType: "FORM", |
|||
loading: true |
|||
}).then((res) => { |
|||
_this.cookie = res.data |
|||
console.log('=======', res) |
|||
}, (err) => { |
|||
clearInterval(timeOut) |
|||
this.countdown = 60 |
|||
// 失败重置状态 |
|||
this.background = "#2fa1f0" |
|||
|
|||
this.isCanClick = true |
|||
this.textDetail = "获取验证码" |
|||
}) |
|||
// } |
|||
} |
|||
} |
|||
}, |
|||
props: ['phoneNum', 'url', 'data', 'sendByCookie'], |
|||
|
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
@ -0,0 +1,245 @@ |
|||
<template> |
|||
<view class="container999" @touchstart="refreshStart" @touchmove="refreshMove" @touchend="refreshEnd"> |
|||
|
|||
<!-- 刷新组件需搭配scroll-view使用,并在pages.json中添加 "disableScroll":true--> |
|||
<tabRefresh ref="tabRefresh" @isRefresh='isRefresh'></tabRefresh> |
|||
|
|||
<view class='nav'> |
|||
|
|||
<!-- 状态栏 --> |
|||
<view :style="{'height':statusBarHeightPx}"></view> |
|||
|
|||
<!-- 导航栏 --> |
|||
<view |
|||
:style="{'height':navHeightPx,'width':navWidthPx,'display': 'flex','flex-direction':'row','box-sizing': 'border-box','justify-content': 'space-between'}"> |
|||
<view |
|||
:style="{'height':navHeightPx,'display': 'flex','flex-direction':'row','box-sizing': 'border-box'}"> |
|||
<image :style="{'height':navHeightPx,'width':iconPx}" mode="aspectFit" :src="backIcon" |
|||
@click="clickBack"></image> |
|||
<text |
|||
:style="{'height':navHeightPx,'line-height':navHeightPx,'font-size':'33rpx','color':'#FFFFFF'}">{{text}}</text> |
|||
</view> |
|||
|
|||
<view |
|||
:style="{'height':navHeightPx,'display': 'flex','flex-direction':'row','box-sizing': 'border-box'}"> |
|||
<view v-if="useTitleLeftBtn!=0" |
|||
:style="{'height':navHeightPx,'display': 'flex','flex-direction':'row','box-sizing': 'border-box'}" |
|||
@click="clickLeftBtn"> |
|||
<image v-if="useTitleLeftBtn == 2" :style="{'height':navHeightPx,'width':navHeightPx}" |
|||
mode="aspectFit" :src="titleLeftBtnSource"></image> |
|||
<text v-if="useTitleLeftBtn == 1" |
|||
:style="{'height':navHeightPx,'line-height':navHeightPx,'font-size':'28rpx','color':'#FFFFFF','padding-left':'20rpx','padding-right':'20rpx'}">{{titleLeftBtnSource}}</text> |
|||
</view> |
|||
<view v-if="useTitleRightBtn!=0" |
|||
:style="{'height':navHeightPx,'display': 'flex','flex-direction':'row','box-sizing': 'border-box'}" |
|||
@click="clickRightBtn"> |
|||
<image v-if="useTitleRightBtn==2" :style="{'height':navHeightPx,'width':navHeightPx}" |
|||
mode="aspectFit" :src="titleRightBtnSource"></image> |
|||
<text v-if="useTitleRightBtn==1" |
|||
:style="{'height':navHeightPx,'line-height':navHeightPx,'font-size':'28rpx','color':'#FFFFFF','padding-left':'20rpx','padding-right':'20rpx'}">{{titleRightBtnSource}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- Tab切换 --> |
|||
<tabTop ref="navTab" :tabTitle="tabTitleData" :tabNum="tabNumData" @onTabClick='clickTab'></tabTop> |
|||
|
|||
</view> |
|||
|
|||
<slot></slot> |
|||
|
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
const util = require('@/util/util.js'); |
|||
export default { |
|||
props: { |
|||
text: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
isShareIn: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
hasBack: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
useTitleLeftBtn: { |
|||
type: String, |
|||
// 0不使用 1使用文字 2使用图片 |
|||
default: "0" |
|||
}, |
|||
useTitleRightBtn: { |
|||
type: String, |
|||
// 0不使用 1使用文字 2使用图片 |
|||
default: "0" |
|||
}, |
|||
titleLeftBtnSource: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
titleRightBtnSource: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
tabTitleData: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
tabNumData: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
currentTab: 0, //sweiper所在页 |
|||
swiperPaddindTop: 0, |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
statusBarH: 0, // 状态栏高度 |
|||
statusBarHeight: "", |
|||
navHeight: "", |
|||
navWidth: "", |
|||
totalHeight: "", |
|||
statusBarHeightPx: "", |
|||
navHeightPx: "", |
|||
navWidthPx: "", |
|||
totalHeightPx: "", |
|||
bg: "#2fa1f0", |
|||
totalHeightUpx: "", |
|||
backIcon: "", |
|||
iconPx: "" |
|||
}; |
|||
}, |
|||
methods: { |
|||
getViewPagerTop() { |
|||
return this.swiperPaddindTop |
|||
}, |
|||
clickLeftBtn() { |
|||
this.$emit("leftBtn", "左侧按钮") |
|||
}, |
|||
clickRightBtn() { |
|||
this.$emit("rightBtn", "右侧按钮") |
|||
}, |
|||
clickBack() { |
|||
if (this.hasBack) { |
|||
if (this.$props.isShareIn) { |
|||
uni.switchTab({ |
|||
url: "../../pages/home/FindFragment" |
|||
}) |
|||
} else { |
|||
this.Back() |
|||
} |
|||
} |
|||
}, |
|||
// 刷新touch监听 |
|||
refreshStart(e) { |
|||
this.$refs.tabRefresh.refreshStart(e); |
|||
}, |
|||
refreshMove(e) { |
|||
this.$refs.tabRefresh.refreshMove(e); |
|||
}, |
|||
refreshEnd(e) { |
|||
this.$refs.tabRefresh.refreshEnd(e); |
|||
}, |
|||
isRefresh() { |
|||
this.$emit("downRefresh", this.currentTab) |
|||
}, |
|||
// 下拉刷新完成主动调用 |
|||
downRefresh() { |
|||
this.$refs.tabRefresh.endAfter() |
|||
}, |
|||
// 主动点击tab |
|||
clickTab(index) { |
|||
// 对外提供点击的index(使用控件的页面更改data里currentTab值) |
|||
// 保证能跟着滚动 |
|||
this.currentTab = index |
|||
this.$emit("tabClickItem", index) |
|||
}, // swiper 滑动 |
|||
changeTab(e) { |
|||
var index = e.detail.current //获取索引 |
|||
// 模拟tab点击 |
|||
this.$refs.navTab.longClick(index); |
|||
} |
|||
}, |
|||
watch: { |
|||
|
|||
isShareIn(newValue) { |
|||
|
|||
if (this.$props.hasBack) { |
|||
this.backIcon = '../../static/custom-icon/back.png' |
|||
this.iconPx = this.navHeightPx |
|||
|
|||
if (this.$props.isShareIn) { |
|||
this.backIcon = '../../static/custom-icon/home.png' |
|||
} |
|||
|
|||
} else { |
|||
this.backIcon = '../../static/img/public/no-back.png' |
|||
this.iconPx = this.navHeight / 8 * 3 + 'px' |
|||
} |
|||
|
|||
} |
|||
|
|||
}, |
|||
created() { |
|||
this.navWidth = getApp().globalData.navWidth |
|||
this.navHeight = getApp().globalData.navHeight |
|||
this.totalHeight = getApp().globalData.totalHeight |
|||
this.statusBarHeight = getApp().globalData.statusBarHeight |
|||
this.navWidthPx = getApp().globalData.navWidthPx |
|||
this.navHeightPx = getApp().globalData.navHeightPx |
|||
this.totalHeightPx = getApp().globalData.totalHeightPx |
|||
this.statusBarHeightPx = getApp().globalData.statusBarHeightPx |
|||
this.totalHeightUpx = getApp().globalData.totalHeightUpx |
|||
|
|||
if (this.$props.hasBack) { |
|||
this.backIcon = '../../static/img/public/back.png' |
|||
this.iconPx = this.navHeightPx |
|||
|
|||
if (this.$props.isShareIn) { |
|||
this.backIcon = '../../static/img/public/home.png' |
|||
} |
|||
|
|||
} else { |
|||
this.backIcon = '../../static/custom-icon/no-back.png' |
|||
this.iconPx = this.navHeight / 8 * 3 + 'px' |
|||
} |
|||
|
|||
|
|||
this.swiperPaddindTop = this.totalHeight + 45 + 'px' |
|||
} |
|||
|
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.container999 { |
|||
width: 100vw; |
|||
font-size: 28upx; |
|||
min-height: 100vh; |
|||
overflow: hidden; |
|||
color: #6B8082; |
|||
position: relative; |
|||
} |
|||
|
|||
.nav { |
|||
position: fixed; |
|||
left: 0; |
|||
top: 0; |
|||
color: white; |
|||
width: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
font-size: 24upx; |
|||
background-color: #2fa1f0; |
|||
z-index: 996; |
|||
} |
|||
</style> |
@ -0,0 +1,93 @@ |
|||
<template> |
|||
|
|||
<view class="user-item-content" @click="click"> |
|||
<view class="user-item-left"> |
|||
<image :src="src" style="width: 70rpx;height: 70rpx;" mode="aspectFit"></image> |
|||
<text class="user-item-textBlack">{{text}}</text> |
|||
</view> |
|||
<view class="user-item-right" style="display: flex;align-items: center;"> |
|||
<text v-if="showRightText" |
|||
style="font-size:30rpx ;color: #999999;margin-top: -7rpx;">{{rightText}}</text> |
|||
<image v-if="showRightImg" style="height: 30rpx;width: 30rpx;" src="../../static/img/public/more.png"></image> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
src: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
text: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
clickId: { |
|||
type: String, |
|||
default: "0" |
|||
}, |
|||
rightText: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
showRightText: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
showRightImg: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
}, |
|||
data() { |
|||
return { |
|||
|
|||
}; |
|||
}, |
|||
methods: { |
|||
click() { |
|||
var clickId = this.$props.clickId; |
|||
this.$emit("click", clickId) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.user-item-content { |
|||
width: 100%; |
|||
height: 90rpx; |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
padding-left: 40rpx; |
|||
justify-content: space-between; |
|||
border-bottom: 0.1px solid #F1F1F1; |
|||
box-sizing: border-box; |
|||
|
|||
.user-item-left { |
|||
display: flex; |
|||
flex-direction: row; |
|||
height: 89rpx; |
|||
align-items: center; |
|||
flex: 1; |
|||
box-sizing: border-box; |
|||
|
|||
.user-item-textBlack { |
|||
color: #333333; |
|||
font-size: 30rpx; |
|||
height: 89rpx; |
|||
line-height: 89rpx; |
|||
} |
|||
} |
|||
|
|||
.user-item-right { |
|||
margin-right: 35rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,516 @@ |
|||
<!-- |
|||
* @插件:日期时间选择器 |
|||
* @作者:陈万照 |
|||
* @公司:山东标梵互动信息技术有限公司 |
|||
* @官网:http://biaofun.com/ |
|||
* @微信:C207668802 |
|||
* @QQ:207668802 |
|||
* @邮箱:cwz@biaofun.com || 207668802@qq.com |
|||
* @版本:v1.0.8 |
|||
--> |
|||
<template> |
|||
<view> |
|||
<picker :mode="mode" :range="range" range-key="text" @change="change" @columnchange="columnchange" |
|||
:value="value" :disa:bled="disabled"> |
|||
<slot></slot> |
|||
</picker> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import utils from '@/common/utils.js'; // 封装的工具集 |
|||
export default { |
|||
/** |
|||
* 数据 |
|||
*/ |
|||
props: { |
|||
// 类型 |
|||
mode: { |
|||
type: String, |
|||
default: "multiSelector" |
|||
}, |
|||
// 是否禁用 |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
|
|||
// 占位符 |
|||
placeholder: { |
|||
type: String, |
|||
default: '请选择日期时间' |
|||
}, |
|||
|
|||
// 表示有效日期时间范围的开始, |
|||
// 字符串格式为 "YYYY-MM-DD hh:mm" |
|||
start: { |
|||
type: String, |
|||
default: '1970-1-1 00:00' |
|||
}, |
|||
|
|||
// 表示有效日期时间范围的结束 |
|||
// 字符串格式为 "YYYY-MM-DD hh:mm" |
|||
end: { |
|||
type: String, |
|||
default: '2300-1-1 00:00' |
|||
}, |
|||
|
|||
// 表示选择器的粒度,有效值:year | month | day | hour | minute |
|||
fields: { |
|||
type: String, |
|||
default: 'minute' |
|||
}, |
|||
|
|||
// 默认值 |
|||
// 字符串格式为 "YYYY-MM-DD hh:mm" |
|||
defaultValue: { |
|||
type: String, |
|||
default: '' |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 数据 |
|||
*/ |
|||
data() { |
|||
return { |
|||
range: [], |
|||
value: [], |
|||
dateStr: '', // 最终显示的字符串 |
|||
dtStart: null, // 有效范围开始 |
|||
dtEnd: null, // 有效范围结束 |
|||
}; |
|||
}, |
|||
|
|||
/** |
|||
* 监听数据 |
|||
*/ |
|||
watch: { |
|||
// 默认值 |
|||
defaultValue() { |
|||
// 设置默认值 |
|||
this.setDefaultValue(); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 组件初次加载完成 |
|||
*/ |
|||
mounted() { |
|||
// 有效日期开始和结束 |
|||
let start = this.start; |
|||
let end = this.end; |
|||
|
|||
// 验证是否是有效的开始和结束日期 |
|||
if (!utils.isString(this.start)) { |
|||
console.log('开始日期需为String类型,格式为 "YYYY-MM-DD hh:mm"'); |
|||
start = '1970-1-1 00:00'; |
|||
} |
|||
if (!utils.isString(this.start)) { |
|||
console.log('结束日期需为String类型,格式为 "YYYY-MM-DD hh:mm"'); |
|||
start = '2300-1-1 00:00'; |
|||
} |
|||
|
|||
// 将开始日期和结束日期转为 Date |
|||
let dtStart = utils.formatDate(start).dt; |
|||
let dtEnd = utils.formatDate(end).dt; |
|||
|
|||
// 判断有效日期结束是否大于有效日期开始,如果不是,则将有效日期结束修改为有效日期开始往后300年 |
|||
if (dtEnd <= dtStart) { |
|||
dtEnd = utils.formatDate(start).dt; |
|||
dtEnd.setFullYear(dtStart.getFullYear() + 300); |
|||
dtEnd.setDate(dtEnd.getDate() - 1); |
|||
} |
|||
|
|||
// 更新开始日期和结束日期 |
|||
this.dtStart = dtStart; |
|||
this.dtEnd = dtEnd; |
|||
|
|||
// 设置默认值 |
|||
this.setDefaultValue(); |
|||
}, |
|||
|
|||
/** |
|||
* 方法 |
|||
*/ |
|||
methods: { |
|||
/** |
|||
* 确认选择 |
|||
*/ |
|||
change(event) { |
|||
let year, month, day, hour, minute; |
|||
if (this.fields == 'year') { |
|||
year = this.range[0][this.value[0]].number; // 年 |
|||
let dtStr = `${year}`; |
|||
this.setDateStr(dtStr); |
|||
this.$emit('change', utils.formatDate(dtStr)); |
|||
return; |
|||
} else if (this.fields == 'month') { |
|||
year = this.range[0][this.value[0]].number; // 年 |
|||
month = this.range[1][this.value[1]].number; // 月 |
|||
let dtStr = `${year}-${month}`; |
|||
this.setDateStr(dtStr); |
|||
this.$emit('change', utils.formatDate(dtStr)); |
|||
return; |
|||
} else if (this.fields == 'day') { |
|||
year = this.range[0][this.value[0]].number; // 年 |
|||
month = this.range[1][this.value[1]].number; // 月 |
|||
day = this.range[2][this.value[2]].number; // 日 |
|||
let dtStr = `${year}-${month}-${day}`; |
|||
this.setDateStr(dtStr); |
|||
this.$emit('change', utils.formatDate(dtStr)); |
|||
return; |
|||
} else if (this.fields == 'hour') { |
|||
year = this.range[0][this.value[0]].number; // 年 |
|||
month = this.range[1][this.value[1]].number; // 月 |
|||
day = this.range[2][this.value[2]].number; // 日 |
|||
hour = this.range[3][this.value[3]].number; // 时 |
|||
day = this.range[2][this.value[2]].number; // 日 |
|||
let dtStr = `${year}-${month}-${day} ${hour}`; |
|||
this.setDateStr(dtStr); |
|||
this.$emit('change', utils.formatDate(dtStr)); |
|||
return; |
|||
} else if (this.fields == 'minute') { |
|||
year = this.range[0][this.value[0]].number; // 年 |
|||
month = this.range[1][this.value[1]].number; // 月 |
|||
day = this.range[2][this.value[2]].number; // 日 |
|||
hour = this.range[3][this.value[3]].number; // 时 |
|||
minute = this.range[4][this.value[4]].number; // 分 |
|||
let dtStr = `${year}-${month}-${day} ${hour}:${minute}`; |
|||
this.setDateStr(dtStr); |
|||
this.$emit('change', utils.formatDate(dtStr)); |
|||
return; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 设置显示的值 |
|||
* @param {Date|String} date 日期字符串或日期对象 |
|||
*/ |
|||
setDateStr(date) { |
|||
let dt = utils.formatDate(date); |
|||
if (this.fields == 'year') { |
|||
this.dateStr = `${dt.YYYY}年`; |
|||
return; |
|||
} |
|||
if (this.fields == 'month') { |
|||
this.dateStr = `${dt.YYYY}年${dt.M}月`; |
|||
return; |
|||
} |
|||
if (this.fields == 'day') { |
|||
this.dateStr = `${dt.YYYY}年${dt.M}月${dt.D}日`; |
|||
return; |
|||
} |
|||
if (this.fields == 'hour') { |
|||
this.dateStr = `${dt.YYYY}年${dt.M}月${dt.D}日 ${dt.h}时`; |
|||
return; |
|||
} |
|||
this.dateStr = `${dt.YYYY}年${dt.M}月${dt.D}日 ${dt.h}时${dt.m}分`; |
|||
}, |
|||
|
|||
/** |
|||
* 设置年数据 |
|||
*/ |
|||
setYearData() { |
|||
// 有效日期 |
|||
let yearStart = this.dtStart.getFullYear(); |
|||
let yearEnd = this.dtEnd.getFullYear(); |
|||
// 年 |
|||
let years = []; |
|||
for (let year = yearStart; year <= yearEnd; year++) { |
|||
let item = { |
|||
number: year, |
|||
text: `${year}年`, |
|||
}; |
|||
years.push(item); |
|||
} |
|||
this.range.splice(0, 1, years); |
|||
}, |
|||
|
|||
/** |
|||
* 设置月数据 |
|||
* @param {Number} year 年 |
|||
*/ |
|||
setMonthData(year) { |
|||
// 有效日期 |
|||
let yearStart = this.dtStart.getFullYear(); |
|||
let monthStart = this.dtStart.getMonth() + 1; |
|||
let yearEnd = this.dtEnd.getFullYear(); |
|||
let monthEnd = this.dtEnd.getMonth() + 1; |
|||
|
|||
// 月 |
|||
let months = []; |
|||
let monthStartIndex = year == yearStart ? monthStart : 1; |
|||
let monthEndIndex = year == yearEnd ? monthEnd : 12; |
|||
for (let month = monthStartIndex; month <= monthEndIndex; month++) { |
|||
let item = { |
|||
number: month, |
|||
text: `${month}月`, |
|||
}; |
|||
months.push(item); |
|||
} |
|||
this.range.splice(1, 1, months); |
|||
}, |
|||
|
|||
/** |
|||
* 设置日数据 |
|||
* @param {Number} year 年 |
|||
* @param {Number} month 月 |
|||
*/ |
|||
setDayData(year, month) { |
|||
// 有效日期 |
|||
let yearStart = this.dtStart.getFullYear(); |
|||
let monthStart = this.dtStart.getMonth() + 1; |
|||
let dayStart = this.dtStart.getDate(); |
|||
let yearEnd = this.dtEnd.getFullYear(); |
|||
let monthEnd = this.dtEnd.getMonth() + 1; |
|||
let dayEnd = this.dtEnd.getDate(); |
|||
|
|||
// 日 |
|||
let days = []; |
|||
let dayStartIndex = year == yearStart && month == monthStart ? dayStart : 1; |
|||
let dayEndIndex; |
|||
if (year == yearEnd && month == monthEnd) { |
|||
dayEndIndex = dayEnd; |
|||
} else { |
|||
dayEndIndex = (new Date(year, month, 0)).getDate(); |
|||
} |
|||
for (let day = dayStartIndex; day <= dayEndIndex; day++) { |
|||
let item = { |
|||
number: day, |
|||
text: `${day}日`, |
|||
}; |
|||
days.push(item); |
|||
} |
|||
this.range.splice(2, 1, days); |
|||
}, |
|||
|
|||
/** |
|||
* 设置时数据 |
|||
* @param {Number} year 年 |
|||
* @param {Number} month 月 |
|||
* @param {Number} day 日 |
|||
*/ |
|||
setHourData(year, month, day) { |
|||
// 有效日期 |
|||
let yearStart = this.dtStart.getFullYear(); |
|||
let monthStart = this.dtStart.getMonth() + 1; |
|||
let dayStart = this.dtStart.getDate(); |
|||
let hourStart = this.dtStart.getHours(); |
|||
let yearEnd = this.dtEnd.getFullYear(); |
|||
let monthEnd = this.dtEnd.getMonth() + 1; |
|||
let dayEnd = this.dtEnd.getDate(); |
|||
let hourEnd = this.dtEnd.getHours(); |
|||
|
|||
// 时 |
|||
let hours = []; |
|||
let hourStartIndex = year == yearStart && month == monthStart && day == dayStart ? hourStart : 0; |
|||
let hourEndIndex = year == yearEnd && month == monthEnd && day == dayEnd ? hourEnd : 23; |
|||
for (let hour = hourStartIndex; hour <= hourEndIndex; hour++) { |
|||
let item = { |
|||
number: hour, |
|||
text: `${hour}时`, |
|||
}; |
|||
hours.push(item); |
|||
} |
|||
this.range.splice(3, 1, hours); |
|||
}, |
|||
|
|||
/** |
|||
* 设置分数据 |
|||
* @param {Number} year 年 |
|||
* @param {Number} month 月 |
|||
* @param {Number} day 日 |
|||
* @param {Number} hour 时 |
|||
*/ |
|||
setMinuteData(year, month, day, hour) { |
|||
// 有效日期 |
|||
let yearStart = this.dtStart.getFullYear(); |
|||
let monthStart = this.dtStart.getMonth() + 1; |
|||
let dayStart = this.dtStart.getDate(); |
|||
let hourStart = this.dtStart.getHours(); |
|||
let minuteStart = this.dtStart.getMinutes(); |
|||
let yearEnd = this.dtEnd.getFullYear(); |
|||
let monthEnd = this.dtEnd.getMonth() + 1; |
|||
let dayEnd = this.dtEnd.getDate(); |
|||
let hourEnd = this.dtEnd.getHours(); |
|||
let minuteEnd = this.dtEnd.getMinutes(); |
|||
|
|||
// 分 |
|||
let minutes = []; |
|||
let minuteStartIndex = year == yearStart && month == monthStart && day == dayStart && hour == hourStart ? |
|||
minuteStart : 0; |
|||
let minuteEndIndex = year == yearEnd && month == monthEnd && day == dayEnd && hour == hourEnd ? minuteEnd : |
|||
59; |
|||
for (let minute = minuteStartIndex; minute <= minuteEndIndex; minute++) { |
|||
let item = { |
|||
number: minute, |
|||
text: `${minute}分`, |
|||
} |
|||
minutes.push(item); |
|||
} |
|||
this.range.splice(4, 1, minutes); |
|||
}, |
|||
|
|||
/** |
|||
* 设置默认值 |
|||
*/ |
|||
setDefaultValue() { |
|||
// 默认日期 |
|||
let dtDefault; |
|||
|
|||
// 开始日期和结束日期 |
|||
let dtStart = this.dtStart; |
|||
let dtEnd = this.dtEnd; |
|||
|
|||
// 判断是否传了默认日期 |
|||
// 传了默认日期,格式化默认日期为日期对象 |
|||
if (this.defaultValue) { |
|||
dtDefault = utils.formatDate(this.defaultValue).dt; |
|||
} |
|||
// 如果没有传默认日期,将默认日期设置为当前日期 |
|||
else { |
|||
dtDefault = new Date(); |
|||
} |
|||
|
|||
// 如果默认日期不在有效日期范围内,设置默认日期为有效日期开始值 |
|||
if (dtDefault < dtStart || dtDefault > dtEnd) { |
|||
dtDefault = dtStart; |
|||
} |
|||
|
|||
// 更新 dateStr |
|||
if (this.defaultValue) this.setDateStr(dtDefault); |
|||
|
|||
// 默认值相关数据 |
|||
let dfYear = dtDefault.getFullYear(); |
|||
let dfMonth = dtDefault.getMonth() + 1; |
|||
let dfDay = dtDefault.getDate(); |
|||
let dfHour = dtDefault.getHours(); |
|||
let dfMinute = dtDefault.getMinutes(); |
|||
|
|||
// 设置年数据 |
|||
this.setYearData(); |
|||
// 设置 Year 这一列的 value 值 |
|||
let yearIndex = this.range[0].findIndex(year => { |
|||
return dfYear == year.number; |
|||
}); |
|||
this.value.splice(0, 1, yearIndex >= 0 ? yearIndex : 0); |
|||
|
|||
// 设置月数据 |
|||
if (this.fields == 'year') return; |
|||
this.setMonthData(dfYear); |
|||
// 设置 Month 这一列的 value 值 |
|||
let monthIndex = this.range[1].findIndex(month => { |
|||
return dfMonth == month.number; |
|||
}); |
|||
this.value.splice(1, 1, monthIndex >= 0 ? monthIndex : 0); |
|||
|
|||
// 设置日数据 |
|||
if (this.fields == 'month') return; |
|||
this.setDayData(dfYear, dfMonth); |
|||
// 设置 Day 这一列的 value 值 |
|||
let dayIndex = this.range[2].findIndex(day => { |
|||
return dfDay == day.number; |
|||
}); |
|||
this.value.splice(2, 1, dayIndex >= 0 ? dayIndex : 0); |
|||
|
|||
// 设置时数据 |
|||
if (this.fields == 'day') return; |
|||
this.setHourData(dfYear, dfMonth, dfDay); |
|||
// 设置 Hour 这一列的 value 值 |
|||
let hourIndex = this.range[3].findIndex(hour => { |
|||
return dfHour == hour.number; |
|||
}); |
|||
this.value.splice(3, 1, hourIndex >= 0 ? hourIndex : 0); |
|||
|
|||
// 设置分数据 |
|||
if (this.fields == 'hour') return; |
|||
this.setMinuteData(dfYear, dfMonth, dfDay, dfHour); |
|||
// 设置 Minute 这一列的 value 值 |
|||
let minuteIndex = this.range[4].findIndex(minute => { |
|||
return dfMinute == minute.number; |
|||
}); |
|||
this.value.splice(4, 1, minuteIndex >= 0 ? minuteIndex : 0); |
|||
}, |
|||
|
|||
/** |
|||
* 某一列的值改变时触发 |
|||
* @param {Number} event.detail.column 表示改变了第几列(下标从0开始) |
|||
* @param {Number} event.detail.value 表示变更值的下标 |
|||
*/ |
|||
columnchange(event) { |
|||
let columnIndex = event.detail.column; // 改变的列的下标 |
|||
let valueIndex = event.detail.value; // 变更值的下标 |
|||
|
|||
// 更新改变列的 value |
|||
this.value.splice(columnIndex, 1, valueIndex); |
|||
|
|||
// 改变年要更新月数据 |
|||
if (this.fields == 'year') return; |
|||
if (columnIndex == 0) { |
|||
// 当前选择的月 |
|||
let monthBeforeUpdate = this.range[1][this.value[1]]; |
|||
// 更新月数据 |
|||
this.setMonthData(this.range[0][this.value[0]].number); |
|||
// 更新 Month Value |
|||
let monthIndex = this.range[1].findIndex(month => { |
|||
return month.number == monthBeforeUpdate.number; |
|||
}); |
|||
this.value.splice(1, 1, monthIndex >= 0 ? monthIndex : 0); |
|||
} |
|||
|
|||
// 改变年、月都要更新日数据 |
|||
if (this.fields == 'month') return; |
|||
if (columnIndex == 0 || columnIndex == 1) { |
|||
// 当前选择的日 |
|||
let dayBeforeUpdate = this.range[2][this.value[2]]; |
|||
// 更新日数据 |
|||
this.setDayData(this.range[0][this.value[0]].number, this.range[1][this.value[1]].number); |
|||
// 更新 Day Value |
|||
let dayIndex = this.range[2].findIndex(day => { |
|||
return day.number == dayBeforeUpdate.number; |
|||
}); |
|||
this.value.splice(2, 1, dayIndex >= 0 ? dayIndex : 0); |
|||
} |
|||
|
|||
// 改变年、月、日都要更新时数据 |
|||
if (this.fields == 'day') return; |
|||
if (columnIndex == 0 || columnIndex == 1 || columnIndex == 2) { |
|||
// 当前选择的时 |
|||
let hourBeforeUpdate = this.range[3][this.value[3]]; |
|||
// 更新时数据 |
|||
this.setHourData(this.range[0][this.value[0]].number, this.range[1][this.value[1]].number, this.range[ |
|||
2][this.value[2]].number); |
|||
// 更新 Hour Value |
|||
let hourIndex = this.range[3].findIndex(hour => { |
|||
return hour.number == hourBeforeUpdate.number; |
|||
}); |
|||
this.value.splice(3, 1, hourIndex >= 0 ? hourIndex : 0); |
|||
} |
|||
|
|||
// 当前选择的分 |
|||
if (this.fields == 'hour') return; |
|||
let minuteBeforeUpdate = this.range[4][this.value[4]]; |
|||
// 更新分数据 |
|||
this.setMinuteData(this.range[0][this.value[0]].number, this.range[1][this.value[1]].number, this.range[2][ |
|||
this.value[2] |
|||
].number, this.range[3][this.value[3]].number); |
|||
// 更新 Minute Value |
|||
let minuteIndex = this.range[4].findIndex(minute => { |
|||
return minute.number == minuteBeforeUpdate.number; |
|||
}); |
|||
this.value.splice(4, 1, minuteIndex >= 0 ? minuteIndex : 0); |
|||
}, |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.content { |
|||
text-align: right; |
|||
} |
|||
|
|||
.placeholder { |
|||
color: #949596; |
|||
} |
|||
</style> |
@ -0,0 +1,30 @@ |
|||
### 组件说明 |
|||
* 日期时间选择器 |
|||
* 组件的默认日期有效期范围为:"1970-01-01 00:00" - "2300-01-01 00:00"。 |
|||
* 注意:如果您传递的日期有效范围的结束日期小于开始日期,则日期有效范围的结束日期会自动修正为开始日期+300年,比如,您传递的日期有效范围的开始日期为 "2020-11-11 18:30", |
|||
* 结束日期为 "2018-11-11 18:30",则此时将会自动修正结束日期为 "2320-11-11 18:30"。 |
|||
* 注意:如果您传递的默认值不在日期有效范围内,则会自动修正默认值为当前日期时间,如果当前日期时间也不在日期有效范围内,则会再次修正为日期有效范围的开始日期。 |
|||
* 注意:该组件用到了我自己封装的 utils.js,位置在 '@/common/js/utils.js'。 |
|||
|
|||
|
|||
### 插件 props 属性 |
|||
* disabled: 是否禁用该组件?Boolean类型; |
|||
* placeholder: 组件没有选中值时显示的内容,String类型; |
|||
* start: 表示有效日期时间范围的开始,String类型,格式为 "YYYY-MM-DD hh:mm"; |
|||
* end: 表示有效日期时间范围的结束,String类型,格式必为 "YYYY-MM-DD hh:mm"; |
|||
* fields: 选择器的粒度,String类型,有效值为 year、month、day、hour、minute; |
|||
* defaultValue: 默认值,String类型,格式为 "YYYY-MM-DD hh:mm"; |
|||
|
|||
### 插件事件 |
|||
- change(date):选择日期时间后的回调事件。 |
|||
* date.YYYY: 年; |
|||
* date.M: 月; |
|||
* date.MM: 月(补0); |
|||
* date.D: 日; |
|||
* date.DD: 日(补0); |
|||
* date.h: 时; |
|||
* date.hh: 时(补0); |
|||
* date.m: 分; |
|||
* date.mm: 分(补0); |
|||
* date.dt: Date对象; |
|||
* ... 还有一些其他的字段,具体看返回值吧! |
@ -0,0 +1,422 @@ |
|||
<template> |
|||
<!-- 城市选择--> |
|||
<view class="city-select"> |
|||
<StatusBar></StatusBar> |
|||
<!-- 预留搜索--> |
|||
<view style="background-color: #FFFFFF;"> |
|||
<view :style="{'height':navHeight,'display': 'flex','box-sizing': 'border-box','flex-direction':'row', |
|||
'align-items':'center','width':width}"> |
|||
<view class="city-serach" v-if="isSearch"> |
|||
<input @input="keyInput" placeholder="请输入城市名称"> |
|||
</view> |
|||
<text style="font-size: 27rpx;margin-right: 20rpx;color: #999999;border-left: 1px solid #f1f1f1;padding-left: 20rpx;" |
|||
@click="close">取消</text> |
|||
</view> |
|||
</view> |
|||
<scroll-view :scroll-top="scrollTop" scroll-y="true" class="city-select-main" id="city-select-main"> |
|||
<!-- 当前定位城市 --> |
|||
<view class="hot-title" v-if="activeCity && !serachCity">当前定位城市</view> |
|||
<view class="hot-city" v-if="activeCity && !serachCity"> |
|||
<view class="hot-item" @click="cityTrigger(activeCity)">{{ activeCity[formatName] }}</view> |
|||
</view> |
|||
<!-- 热门城市 --> |
|||
<view class="hot-title" v-if="hotCity.length > 0 && !serachCity">热门城市</view> |
|||
<view class="hot-city" v-if="hotCity.length > 0 && !serachCity"> |
|||
<template v-for="(item, index) in hotCity"> |
|||
<view :key="index" @click="cityTrigger(item, 'hot')" class="hot-item">{{ item[formatName] }}</view> |
|||
</template> |
|||
</view> |
|||
<!-- 城市列表(搜索前) --> |
|||
<view class="citys" v-if="!serachCity"> |
|||
<view v-for="(city, index) in sortItems" :key="index" v-if="city.isCity"> |
|||
<view class="citys-item-letter" :id="'city-letter-' + (city.name === '#' ? '0' : city.name)">{{ city.name }}</view> |
|||
<view class="citys-item" v-for="(item, inx) in city.citys" :key="inx" @click="cityTrigger(item)">{{ item.cityName }}</view> |
|||
</view> |
|||
</view> |
|||
<!-- 城市列表(搜索后) --> |
|||
<view class="citys" v-if="serachCity"> |
|||
<view v-for="(item, index) in searchDatas" :key="index"> |
|||
<view class="citys-item" :key="index" @click="cityTrigger(item)">{{ item.name }}</view> |
|||
</view> |
|||
</view> |
|||
</scroll-view> |
|||
<!-- 城市选择索引--> |
|||
<view class="city-indexs-view" v-if="!serachCity"> |
|||
<view class="city-indexs"> |
|||
<view v-for="(cityIns, index) in handleCity" v-if="cityIns.isCity" :key="index" @click="cityindex(cityIns.name)">{{ cityIns.name }}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import citySelect from './citySelect.js' |
|||
export default { |
|||
props: { |
|||
//传入要排序的名称 |
|||
formatName: { |
|||
type: String, |
|||
default: 'cityName' |
|||
}, |
|||
//当前定位城市 |
|||
activeCity: { |
|||
type: Object, |
|||
default: () => null |
|||
}, |
|||
//热门城市 |
|||
hotCity: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
//城市数据 |
|||
obtainCitys: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
//是否有搜索 |
|||
isSearch: { |
|||
type: Boolean, |
|||
default: true |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
navHeight: "", |
|||
width: "", |
|||
totalHeight: "", |
|||
scrollTop: 0, //scroll-view 滑动的距离 |
|||
cityindexs: [], // 城市索引 |
|||
activeCityIndex: '', // 当前所在的城市索引 |
|||
handleCity: [], // 处理后的城市数据 |
|||
serachCity: '', // 搜索的城市 |
|||
cityData: [] |
|||
} |
|||
}, |
|||
computed: { |
|||
/** |
|||
* @desc 城市列表排序 |
|||
* @return Array |
|||
*/ |
|||
sortItems() { |
|||
for (let index = 0; index < this.handleCity.length; index++) { |
|||
if (this.handleCity[index].isCity) { |
|||
var cityArr = this.handleCity[index].citys |
|||
cityArr = cityArr.sort(function(a, b) { |
|||
var value1 = a.unicode |
|||
var value2 = b.unicode |
|||
return value1 - value2 |
|||
}) |
|||
} |
|||
} |
|||
return this.handleCity |
|||
}, |
|||
/** |
|||
* @desc 搜索后的城市列表 |
|||
* @return Array |
|||
*/ |
|||
searchDatas() { |
|||
var searchData = [] |
|||
for (let i = 0; i < this.cityData.length; i++) { |
|||
if (this.cityData[i][this.formatName].indexOf(this.serachCity) !== -1) { |
|||
searchData.push({ |
|||
oldData: this.cityData[i], |
|||
name: this.cityData[i][this.formatName] |
|||
}) |
|||
} |
|||
} |
|||
return searchData |
|||
} |
|||
}, |
|||
created() { |
|||
// 初始化城市数据 |
|||
this.cityData = this.obtainCitys |
|||
this.initializationCity() |
|||
this.buildCityindexs() |
|||
// 获取状态栏高度 |
|||
let info = uni.getSystemInfoSync() |
|||
let statusBarHeight = info.statusBarHeight |
|||
this.statusBarHeight = statusBarHeight + 'px' |
|||
|
|||
this.navHeight = '45px' |
|||
this.width = '100%' |
|||
this.totalHeight = 45 + statusBarHeight + 'px' |
|||
|
|||
// 获取胶囊高度 |
|||
// #ifdef MP-WEIXIN||MP-BAID||MP-QQ||MP-TOUTIAO |
|||
let menuButton = uni.getMenuButtonBoundingClientRect() |
|||
let top = menuButton.top |
|||
let bottom = menuButton.bottom |
|||
let navHeight = bottom - top + (top - statusBarHeight) * 2 + 4 |
|||
this.navHeight = navHeight + 'px' |
|||
this.width = menuButton.left + 'px' |
|||
this.totalHeight = navHeight + statusBarHeight + 'px' |
|||
// #endif |
|||
}, |
|||
watch: { |
|||
obtainCitys(newData) { |
|||
this.updateCitys(newData) |
|||
} |
|||
}, |
|||
methods: { |
|||
/** |
|||
* @desc 初始化 |
|||
*/ |
|||
updateCitys(data) { |
|||
if (data && data.length) { |
|||
this.cityData = data |
|||
this.initializationCity() |
|||
this.buildCityindexs() |
|||
} |
|||
}, |
|||
/** |
|||
* @desc 监听输入框的值 |
|||
*/ |
|||
keyInput(event) { |
|||
this.serachCity = event.detail.value |
|||
}, |
|||
/** |
|||
* @desc 初始化城市数据 |
|||
* @return undefind |
|||
*/ |
|||
initializationCity() { |
|||
this.handleCity = [] |
|||
const cityLetterArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', |
|||
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#' |
|||
] |
|||
for (let index = 0; index < cityLetterArr.length; index++) { |
|||
this.handleCity.push({ |
|||
name: cityLetterArr[index], |
|||
isCity: false, // 用于区分是否含有当前字母开头的城市 |
|||
citys: [] // 存放城市首字母含是此字母的数组 |
|||
}) |
|||
} |
|||
}, |
|||
/** |
|||
* @desc 得到城市的首字母 |
|||
* @param str String |
|||
*/ |
|||
getLetter(str) { |
|||
return citySelect.getFirstLetter(str[0]) |
|||
}, |
|||
/** |
|||
* @desc 构建城市索引 |
|||
* @return undefind |
|||
*/ |
|||
buildCityindexs() { |
|||
this.cityindexs = [] |
|||
for (let i = 0; i < this.cityData.length; i++) { |
|||
// 获取首字母 |
|||
let cityLetter = this.getLetter(this.cityData[i][this.formatName]).firstletter |
|||
// 获取当前城市首字母的unicode,用作后续排序 |
|||
let unicode = this.getLetter(this.cityData[i][this.formatName]).unicode |
|||
|
|||
let index = this.cityIndexPosition(cityLetter) |
|||
if (this.cityindexs.indexOf(cityLetter) === -1) { |
|||
this.handleCity[index].isCity = true |
|||
this.cityindexs.push(cityLetter) |
|||
} |
|||
|
|||
this.handleCity[index].citys.push({ |
|||
cityName: this.cityData[i][this.formatName], |
|||
unicode: unicode, |
|||
oldData: this.cityData[i] |
|||
}) |
|||
} |
|||
}, |
|||
/** |
|||
* @desc 滑动到城市索引所在的地方 |
|||
* @param id String 城市索引 |
|||
*/ |
|||
cityindex(id) { |
|||
//创建节点查询器 |
|||
const query = uni.createSelectorQuery().in(this) |
|||
var that = this |
|||
that.scrollTop = 0 |
|||
//滑动到指定位置(解决方法:重置到顶部,重新计算,影响:页面会闪一下) |
|||
setTimeout(() => { |
|||
query |
|||
.select('#city-letter-' + (id === '#' ? '0' : id)) |
|||
.boundingClientRect(data => { |
|||
// console.log("得到布局位置信息" + JSON.stringify(data)); |
|||
// console.log("节点离页面顶部的距离为" + data.top); |
|||
data ? (that.scrollTop = data.top) : void 0 |
|||
}) |
|||
.exec() |
|||
}, 0) |
|||
}, |
|||
/** |
|||
* @desc 获取城市首字母的unicode |
|||
* @param letter String 城市索引 |
|||
*/ |
|||
cityIndexPosition(letter) { |
|||
if (!letter) { |
|||
return '' |
|||
} |
|||
const ACode = 65 |
|||
return letter === '#' ? 26 : letter.charCodeAt(0) - ACode |
|||
}, |
|||
/** @desc 城市列表点击事件 |
|||
* @param Object |
|||
*/ |
|||
cityTrigger(item, isHot) { |
|||
// 传值到父组件 |
|||
this.$emit('cityClick', item.oldData ? item.oldData : item) |
|||
}, |
|||
close() { |
|||
this.Back() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
//宽度转换vw |
|||
@function vww($number) { |
|||
@return ($number / 375) * 750+rpx; |
|||
} |
|||
|
|||
view { |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.city-serach { |
|||
flex: 1; |
|||
color: #4a4a4a; |
|||
padding: 0 vww(10); |
|||
|
|||
&-input { |
|||
margin: vww(13) 0; |
|||
height: vww(40); |
|||
line-height: vww(40); |
|||
font-size: vww(14); |
|||
padding: 0 vww(5); |
|||
border: 1px solid #4d8cfd; |
|||
border-radius: 3px; |
|||
} |
|||
} |
|||
|
|||
.city-select-main { |
|||
position: relative; |
|||
// overflow: scroll; |
|||
// -webkit-overflow-scrolling: touch; |
|||
width: 100%; |
|||
height: 100%; |
|||
background: #FFFFFF; |
|||
// overflow-y: auto; |
|||
} |
|||
|
|||
.city-select { |
|||
position: relative; |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background: #f6f5fa; |
|||
|
|||
// 热门城市 |
|||
.hot-title { |
|||
padding-left: vww(13); |
|||
width: 100vw; |
|||
font-size: 14px; |
|||
line-height: vww(30); |
|||
color: #9b9b9b; |
|||
background-color: #ededed; |
|||
} |
|||
|
|||
.hot-city { |
|||
padding-left: vww(23); |
|||
padding-right: vww(20); |
|||
padding-top: vww(12); |
|||
overflow: hidden; |
|||
width: 100vw; |
|||
|
|||
.hot-item { |
|||
float: left; |
|||
padding: 0 vww(5); |
|||
margin-right: vww(16); |
|||
margin-bottom: vww(12); |
|||
overflow: hidden; |
|||
width: vww(100); |
|||
height: vww(31); |
|||
font-size: 14px; |
|||
text-align: center; |
|||
border-radius: vww(5); |
|||
|
|||
display: -webkit-box; |
|||
-webkit-box-orient: vertical; |
|||
-webkit-line-clamp: 1; |
|||
|
|||
line-height: vww(31); |
|||
color: #4a4a4a; |
|||
background: #f5f5f5; |
|||
|
|||
&:nth-child(3n) { |
|||
margin-right: 0; |
|||
} |
|||
} |
|||
|
|||
.hot-hidden { |
|||
display: none; |
|||
margin-right: 0; |
|||
} |
|||
} |
|||
|
|||
.citys { |
|||
>view { |
|||
padding-left: vww(18); |
|||
width: 100%; |
|||
font-size: 14px; |
|||
background: #fff; |
|||
|
|||
.citys-item-letter { |
|||
margin-left: vww(-18); |
|||
padding-left: vww(18); |
|||
margin-top: -1px; |
|||
width: 100vw; |
|||
line-height: vww(30); |
|||
color: #9b9b9b; |
|||
background: #ededed; |
|||
border-top: none; |
|||
} |
|||
|
|||
.citys-item { |
|||
width: 100%; |
|||
line-height: vww(50); |
|||
color: #4a4a4a; |
|||
border-bottom: 0.1px solid #ebebf0; |
|||
|
|||
&:last-child { |
|||
border: none; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.city-indexs-view { |
|||
position: absolute; |
|||
right: 0; |
|||
top: 0; |
|||
z-index: 999; |
|||
display: flex; |
|||
width: vww(20); |
|||
height: 100%; |
|||
text-align: center; |
|||
|
|||
.city-indexs { |
|||
width: vww(20); |
|||
text-align: center; |
|||
vertical-align: middle; |
|||
align-self: center; |
|||
|
|||
>view { |
|||
margin-bottom: vww(10); |
|||
width: vww(20); |
|||
font-size: 12px; |
|||
color: #4d8cfd; |
|||
|
|||
&:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
@ -0,0 +1,697 @@ |
|||
<template> |
|||
<view class="cropper" id="cropper" :class="{ show: show }"> |
|||
<view class="cropper-head"> |
|||
<view class="cropper-btn cropper-reset" @tap="resetCrop">重做</view> |
|||
</view> |
|||
<view class="cropper-body"> |
|||
<image id="image" class="cropper-image" :src="imagePath" mode="aspectFit"></image> |
|||
<view |
|||
:style="{ width: stageWidth + 'px', height: stageHeight + 'px', left: stageLeft + 'px', top: stageTop + 'px' }" |
|||
class="cropper-stage" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="touchMove"> |
|||
<view id="box" class="cropper-box" |
|||
:style="{ width: boxWidth + 'px', height: boxHeight + 'px', left: boxLeft + 'px', top: boxTop + 'px' }"> |
|||
<view id="lt" class="lt"></view> |
|||
<view id="lb" class="lb"></view> |
|||
<view id="rt" class="rt"></view> |
|||
<view id="rb" class="rb"></view> |
|||
|
|||
<view class="line-v" style="left:33.3%;"></view> |
|||
<view class="line-v" style="left:66.6%;"></view> |
|||
<view class="line-h" style="top:33.3%;"></view> |
|||
<view class="line-h" style="top:66.6%;"></view> |
|||
</view> |
|||
</view> |
|||
|
|||
<canvas class="cropper-canvas" canvas-id="canvas" |
|||
:style="{ height: canvasHeight + 'px', width: canvasWidth + 'px' }"></canvas> |
|||
</view> |
|||
<view class="cropper-bottom"> |
|||
<view class="cropper-btn cropper-cancel" @tap="cancelCrop">取消</view> |
|||
<view class="cropper-btn cropper-ok" @tap="completeCrop">裁剪</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
<script> |
|||
//无须渲染的变量 |
|||
|
|||
let layoutLeft = 0; |
|||
let layoutTop = 0; |
|||
let layoutWidth = 0; |
|||
let layoutHeight = 0; |
|||
|
|||
let stageLeft = 0; |
|||
let stageTop = 0; |
|||
let stageWidth = 0; |
|||
let stageHeight = 0; |
|||
|
|||
let imageWidth = 0; |
|||
let imageHeight = 0; |
|||
|
|||
let pixelRatio = 1; //todo设备像素密度//暂不使用// |
|||
|
|||
let imageStageRatio = 1; //图片实际尺寸与剪裁舞台大小的比值,用于尺寸换算。 |
|||
|
|||
let minBoxWidth = 0; |
|||
let minBoxHeight = 0; |
|||
|
|||
let touchStartBoxLeft = 0; |
|||
let touchStartBoxTop = 0; |
|||
let touchStartBoxWidth = 0; |
|||
let touchStartBoxHeight = 0; |
|||
|
|||
let touchStartX = 0; |
|||
let touchStartY = 0; |
|||
|
|||
export default { |
|||
name: 'cropper', |
|||
props: { |
|||
quality: { |
|||
type: Number, |
|||
default: 1 |
|||
}, |
|||
|
|||
//目标文件的类型。默认值为jpg,jpg:输出jpg格式图片;png:输出png格式图片 |
|||
outputFileType: { |
|||
type: String, |
|||
default: 'jpg' |
|||
}, |
|||
//目标图片的宽高比,默认null,即不限制剪裁宽高比。aspectRatio需大于0 |
|||
aspectRatio: { |
|||
type: [Number, null], |
|||
default: null |
|||
}, |
|||
//最小剪裁尺寸与原图尺寸的比率,默认0.15,即宽度最小剪裁到原图的0.15宽。 |
|||
minBoxWidthRatio: { |
|||
type: Number, |
|||
default: 0.15 |
|||
}, |
|||
//同minBoxWidthRatio,当设置aspectRatio时,minBoxHeight值设置无效。minBoxHeight值由minBoxWidth 和 aspectRatio自动计算得到。 |
|||
minBoxHeightRatio: { |
|||
type: Number, |
|||
default: 0.15 |
|||
}, |
|||
//剪裁框初始大小比率。默认值0.8,即剪裁框默认宽度为图片宽度的0.8倍。 |
|||
initialBoxWidthRatio: { |
|||
type: Number, |
|||
default: 0.8 |
|||
}, |
|||
//同initialBoxWidthRatio,当设置aspectRatio时,initialBoxHeightRatio值设置无效。initialBoxHeightRatio值由initialBoxWidthRatio 和 aspectRatio自动计算得到。 |
|||
initialBoxHeightRatio: { |
|||
type: Number, |
|||
default: 0.8 |
|||
}, |
|||
// 保存的模式 None(无限制)ScaleTo500(缩放到指定的尺寸 如 500宽度) Max500(不超过500宽度) |
|||
saveMode: { |
|||
type: String, |
|||
default: "None" |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
//data |
|||
stageLeft: 0, |
|||
stageTop: 0, |
|||
stageWidth: 0, |
|||
stageHeight: 0, |
|||
|
|||
boxWidth: 0, |
|||
boxHeight: 0, |
|||
boxLeft: 0, |
|||
boxTop: 0, |
|||
|
|||
canvasWidth: 0, |
|||
canvasHeight: 0, |
|||
show: false, |
|||
imagePath: '' |
|||
}; |
|||
}, |
|||
methods: { |
|||
resetCrop() { |
|||
this.$emit('reset'); |
|||
this.init(this.imagePath); |
|||
}, |
|||
cancelCrop() { |
|||
this.$emit('cancel'); |
|||
}, |
|||
completeCrop() { |
|||
|
|||
let imagePath = this.imagePath; |
|||
let canvasContext = uni.createCanvasContext('canvas', this); |
|||
|
|||
let boxLeft = this.boxLeft; |
|||
let boxTop = this.boxTop; |
|||
let boxWidth = this.boxWidth; |
|||
let boxHeight = this.boxHeight; |
|||
|
|||
let sx = Math.ceil(boxLeft * imageStageRatio); |
|||
let sy = Math.ceil(boxTop * imageStageRatio); |
|||
|
|||
let sWidth = Math.ceil(boxWidth * imageStageRatio); |
|||
let sHeight = Math.ceil(boxHeight * imageStageRatio); |
|||
|
|||
let dx = 0; |
|||
let dy = 0; |
|||
|
|||
let dWidth = Math.ceil(sWidth * pixelRatio); |
|||
let dHeight = Math.ceil(sHeight * pixelRatio); |
|||
const param = { |
|||
x: sx, |
|||
y: sy, |
|||
width: dWidth, |
|||
height: dHeight, |
|||
rotate: 0, |
|||
scaleX: 1, |
|||
scaleY: 1 |
|||
}; |
|||
|
|||
|
|||
let _this = this |
|||
|
|||
canvasContext.drawImage(imagePath, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); |
|||
canvasContext.draw(false, () => { |
|||
|
|||
let targetWidth = 0 |
|||
let targetHeight = 0 |
|||
let mode = _this.saveMode |
|||
|
|||
if (_this.IsEmpty(mode) || mode == "None") { |
|||
targetWidth = sWidth |
|||
targetHeight = sHeight |
|||
} else { |
|||
|
|||
if (mode.startsWith("ScaleTo")) { |
|||
let w = Number(mode.substring(7, mode.length)) |
|||
if (isNaN(w)) { |
|||
targetWidth = sWidth |
|||
targetHeight = sHeight |
|||
} else { |
|||
// 缩放到指定 |
|||
targetWidth = w |
|||
targetHeight = sHeight * w / sWidth |
|||
} |
|||
} else if (mode.startsWith("Max")) { |
|||
let w = Number(mode.substring(3, mode.length)) |
|||
if (isNaN(w)) { |
|||
targetWidth = sWidth |
|||
targetHeight = sHeight |
|||
} else { |
|||
if (sWidth > w) { |
|||
// 超出缩小 |
|||
targetWidth = w |
|||
targetHeight = sHeight * w / sWidth |
|||
} else { |
|||
targetWidth = sWidth |
|||
targetHeight = sHeight |
|||
} |
|||
} |
|||
} else { |
|||
targetWidth = sWidth |
|||
targetHeight = sHeight |
|||
} |
|||
|
|||
} |
|||
|
|||
uni.canvasToTempFilePath({ |
|||
x: dx, |
|||
y: dy, |
|||
width: dWidth, |
|||
height: dHeight, |
|||
destWidth: targetWidth, |
|||
destHeight: targetHeight, |
|||
canvasId: 'canvas', |
|||
fileType: this.outputFileType, |
|||
quality: this.quality, |
|||
success: res => { |
|||
this.$emit('complete', { |
|||
param, |
|||
path: res.tempFilePath, |
|||
source: this.imagePath |
|||
}); |
|||
} |
|||
}, |
|||
this |
|||
); |
|||
}); |
|||
}, |
|||
touchMove(e) { |
|||
let targetId = e.target.id; |
|||
let touch = e.touches[0]; |
|||
let pageX = touch.pageX; |
|||
let pageY = touch.pageY; |
|||
|
|||
let offsetX = pageX - touchStartX; |
|||
let offsetY = pageY - touchStartY; |
|||
|
|||
if (targetId == 'box') { |
|||
let newBoxLeft = touchStartBoxLeft + offsetX; |
|||
let newBoxTop = touchStartBoxTop + offsetY; |
|||
|
|||
if (newBoxLeft < 0) { |
|||
newBoxLeft = 0; |
|||
} |
|||
if (newBoxTop < 0) { |
|||
newBoxTop = 0; |
|||
} |
|||
if (newBoxLeft + touchStartBoxWidth > stageWidth) { |
|||
newBoxLeft = stageWidth - touchStartBoxWidth; |
|||
} |
|||
if (newBoxTop + touchStartBoxHeight > stageHeight) { |
|||
newBoxTop = stageHeight - touchStartBoxHeight; |
|||
} |
|||
this.boxLeft = newBoxLeft; |
|||
this.boxTop = newBoxTop; |
|||
} else if (targetId == 'lt') { |
|||
if (this.aspectRatio) { |
|||
offsetY = offsetX / this.aspectRatio; |
|||
} |
|||
|
|||
let newBoxLeft = touchStartBoxLeft + offsetX; |
|||
let newBoxTop = touchStartBoxTop + offsetY; |
|||
|
|||
if (newBoxLeft < 0) { |
|||
newBoxLeft = 0; |
|||
} |
|||
if (newBoxTop < 0) { |
|||
newBoxTop = 0; |
|||
} |
|||
|
|||
if (touchStartBoxLeft + touchStartBoxWidth - newBoxLeft < minBoxWidth) { |
|||
newBoxLeft = touchStartBoxLeft + touchStartBoxWidth - minBoxWidth; |
|||
} |
|||
if (touchStartBoxTop + touchStartBoxHeight - newBoxTop < minBoxHeight) { |
|||
newBoxTop = touchStartBoxTop + touchStartBoxHeight - minBoxHeight; |
|||
} |
|||
|
|||
let newBoxWidth = touchStartBoxWidth - (newBoxLeft - touchStartBoxLeft); |
|||
let newBoxHeight = touchStartBoxHeight - (newBoxTop - touchStartBoxTop); |
|||
|
|||
//约束比例 |
|||
if (newBoxTop == 0 && this.aspectRatio && newBoxLeft != 0) { |
|||
newBoxWidth = newBoxHeight * this.aspectRatio; |
|||
newBoxLeft = touchStartBoxWidth - newBoxWidth + touchStartBoxLeft; |
|||
} |
|||
if (newBoxLeft == 0 && this.aspectRatio) { |
|||
newBoxHeight = newBoxWidth / this.aspectRatio; |
|||
newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop; |
|||
} |
|||
|
|||
if (newBoxWidth == minBoxWidth && this.aspectRatio) { |
|||
newBoxHeight = newBoxWidth / this.aspectRatio; |
|||
newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop; |
|||
} |
|||
this.boxTop = newBoxTop; |
|||
this.boxLeft = newBoxLeft; |
|||
this.boxWidth = newBoxWidth; |
|||
this.boxHeight = newBoxHeight; |
|||
} else if (targetId == 'rt') { |
|||
if (this.aspectRatio) { |
|||
offsetY = -offsetX / this.aspectRatio; |
|||
} |
|||
|
|||
let newBoxWidth = touchStartBoxWidth + offsetX; |
|||
if (newBoxWidth < minBoxWidth) { |
|||
newBoxWidth = minBoxWidth; |
|||
} |
|||
if (touchStartBoxLeft + newBoxWidth > stageWidth) { |
|||
newBoxWidth = stageWidth - touchStartBoxLeft; |
|||
} |
|||
|
|||
let newBoxTop = touchStartBoxTop + offsetY; |
|||
|
|||
if (newBoxTop < 0) { |
|||
newBoxTop = 0; |
|||
} |
|||
|
|||
if (touchStartBoxTop + touchStartBoxHeight - newBoxTop < minBoxHeight) { |
|||
newBoxTop = touchStartBoxTop + touchStartBoxHeight - minBoxHeight; |
|||
} |
|||
let newBoxHeight = touchStartBoxHeight - (newBoxTop - touchStartBoxTop); |
|||
|
|||
//约束比例 |
|||
if (newBoxTop == 0 && this.aspectRatio && newBoxWidth != stageWidth - touchStartBoxLeft) { |
|||
newBoxWidth = newBoxHeight * this.aspectRatio; |
|||
} |
|||
|
|||
if (newBoxWidth == stageWidth - touchStartBoxLeft && this.aspectRatio) { |
|||
newBoxHeight = newBoxWidth / this.aspectRatio; |
|||
newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop; |
|||
} |
|||
|
|||
if (newBoxWidth == minBoxWidth && this.aspectRatio) { |
|||
newBoxHeight = newBoxWidth / this.aspectRatio; |
|||
newBoxTop = touchStartBoxHeight - newBoxHeight + touchStartBoxTop; |
|||
} |
|||
|
|||
this.boxTop = newBoxTop; |
|||
this.boxHeight = newBoxHeight; |
|||
this.boxWidth = newBoxWidth; |
|||
} else if (targetId == 'lb') { |
|||
if (this.aspectRatio) { |
|||
offsetY = -offsetX / this.aspectRatio; |
|||
} |
|||
let newBoxLeft = touchStartBoxLeft + offsetX; |
|||
|
|||
if (newBoxLeft < 0) { |
|||
newBoxLeft = 0; |
|||
} |
|||
if (touchStartBoxLeft + touchStartBoxWidth - newBoxLeft < minBoxWidth) { |
|||
newBoxLeft = touchStartBoxLeft + touchStartBoxWidth - minBoxWidth; |
|||
} |
|||
|
|||
let newBoxWidth = touchStartBoxWidth - (newBoxLeft - touchStartBoxLeft); |
|||
|
|||
let newBoxHeight = touchStartBoxHeight + offsetY; |
|||
if (newBoxHeight < minBoxHeight) { |
|||
newBoxHeight = minBoxHeight; |
|||
} |
|||
if (touchStartBoxTop + newBoxHeight > stageHeight) { |
|||
newBoxHeight = stageHeight - touchStartBoxTop; |
|||
} |
|||
|
|||
//约束比例 |
|||
if (newBoxHeight == stageHeight - touchStartBoxTop && this.aspectRatio && newBoxLeft != 0) { |
|||
newBoxWidth = newBoxHeight * this.aspectRatio; |
|||
newBoxLeft = touchStartBoxWidth - newBoxWidth + touchStartBoxLeft; |
|||
} |
|||
if (newBoxLeft == 0 && this.aspectRatio) { |
|||
newBoxHeight = newBoxWidth / this.aspectRatio; |
|||
} |
|||
|
|||
if (newBoxWidth == minBoxWidth && this.aspectRatio) { |
|||
newBoxHeight = newBoxWidth / this.aspectRatio; |
|||
} |
|||
|
|||
this.boxLeft = newBoxLeft; |
|||
this.boxWidth = newBoxWidth; |
|||
this.boxHeight = newBoxHeight; |
|||
} else if (targetId == 'rb') { |
|||
if (this.aspectRatio) { |
|||
offsetY = offsetX / this.aspectRatio; |
|||
} |
|||
let newBoxWidth = touchStartBoxWidth + offsetX; |
|||
if (newBoxWidth < minBoxWidth) { |
|||
newBoxWidth = minBoxWidth; |
|||
} |
|||
if (touchStartBoxLeft + newBoxWidth > stageWidth) { |
|||
newBoxWidth = stageWidth - touchStartBoxLeft; |
|||
} |
|||
|
|||
let newBoxHeight = touchStartBoxHeight + offsetY; |
|||
if (newBoxHeight < minBoxHeight) { |
|||
newBoxHeight = minBoxHeight; |
|||
} |
|||
if (touchStartBoxTop + newBoxHeight > stageHeight) { |
|||
newBoxHeight = stageHeight - touchStartBoxTop; |
|||
} |
|||
|
|||
//约束比例 |
|||
if (newBoxHeight == stageHeight - touchStartBoxTop && this.aspectRatio && newBoxWidth != stageWidth - |
|||
touchStartBoxLeft) { |
|||
newBoxWidth = newBoxHeight * this.aspectRatio; |
|||
} |
|||
|
|||
if (newBoxWidth == stageWidth - touchStartBoxLeft && this.aspectRatio) { |
|||
newBoxHeight = newBoxWidth / this.aspectRatio; |
|||
} |
|||
|
|||
if (newBoxWidth == minBoxWidth && this.aspectRatio) { |
|||
newBoxHeight = newBoxWidth / this.aspectRatio; |
|||
} |
|||
|
|||
this.boxWidth = newBoxWidth; |
|||
this.boxHeight = newBoxHeight; |
|||
} |
|||
}, |
|||
touchStart(e) { |
|||
let touch = e.touches[0]; |
|||
let pageX = touch.pageX; |
|||
let pageY = touch.pageY; |
|||
|
|||
touchStartX = pageX; |
|||
touchStartY = pageY; |
|||
|
|||
touchStartBoxLeft = this.boxLeft; |
|||
touchStartBoxTop = this.boxTop; |
|||
touchStartBoxWidth = this.boxWidth; |
|||
touchStartBoxHeight = this.boxHeight; |
|||
}, |
|||
close(force = true) { |
|||
this.show = false; |
|||
if (force) { |
|||
this.imagePath = '' |
|||
} |
|||
}, |
|||
init(src) { |
|||
|
|||
let _this = this |
|||
|
|||
if (!src) { |
|||
return ''; |
|||
} |
|||
this.imagePath = src; |
|||
uni.showLoading({ |
|||
mask: true, |
|||
title: '载入图片中' |
|||
}); |
|||
uni.createSelectorQuery() |
|||
.in(this) |
|||
.select('.cropper-body') |
|||
.boundingClientRect(rect => { |
|||
|
|||
layoutLeft = rect.left; |
|||
layoutTop = rect.top; |
|||
layoutWidth = rect.width; |
|||
layoutHeight = rect.height; |
|||
|
|||
uni.getImageInfo({ |
|||
src: _this.imagePath, |
|||
success: imageInfo => { |
|||
imageWidth = imageInfo.width; |
|||
imageHeight = imageInfo.height; |
|||
let imageWH = imageWidth / imageHeight; |
|||
let layoutWH = layoutWidth / layoutHeight; |
|||
if (imageWH >= layoutWH) { |
|||
stageWidth = layoutWidth; |
|||
stageHeight = stageWidth / imageWH; |
|||
imageStageRatio = imageHeight / stageHeight; |
|||
} else { |
|||
stageHeight = layoutHeight; |
|||
stageWidth = layoutHeight * imageWH; |
|||
imageStageRatio = imageWidth / stageWidth; |
|||
} |
|||
stageLeft = (layoutWidth - stageWidth) / 2; |
|||
stageTop = (layoutHeight - stageHeight) / 2; |
|||
|
|||
minBoxWidth = stageWidth * _this.minBoxWidthRatio; |
|||
minBoxHeight = stageHeight * _this.minBoxHeightRatio; |
|||
|
|||
let boxWidth = stageWidth * _this.initialBoxWidthRatio; |
|||
let boxHeight = stageHeight * _this.initialBoxHeightRatio; |
|||
|
|||
if (_this.aspectRatio) { |
|||
boxHeight = boxWidth / _this.aspectRatio; |
|||
} |
|||
if (boxHeight > stageHeight) { |
|||
boxHeight = stageHeight; |
|||
boxWidth = boxHeight * _this.aspectRatio; |
|||
} |
|||
|
|||
let boxLeft = (stageWidth - boxWidth) / 2; |
|||
let boxTop = (stageHeight - boxHeight) / 2; |
|||
|
|||
_this.canvasWidth = imageWidth * pixelRatio; |
|||
_this.canvasHeight = imageHeight * pixelRatio; |
|||
|
|||
_this.stageLeft = stageLeft; |
|||
_this.stageTop = stageTop; |
|||
_this.stageWidth = stageWidth; |
|||
_this.stageHeight = stageHeight; |
|||
|
|||
_this.boxWidth = boxWidth; |
|||
_this.boxHeight = boxHeight; |
|||
_this.boxLeft = boxLeft; |
|||
_this.boxTop = boxTop; |
|||
setTimeout(() => { |
|||
uni.hideLoading(); |
|||
_this.show = true; |
|||
}, 100); |
|||
}, |
|||
fail: () => { |
|||
uni.showToast({ |
|||
icon: 'none', |
|||
title: '图片载入失败' |
|||
}); |
|||
} |
|||
}); |
|||
}) |
|||
.exec(); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.cropper { |
|||
position: fixed; |
|||
left: 0; |
|||
right: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
background-color: #000; |
|||
z-index: -1000000; |
|||
opacity: 0; |
|||
|
|||
&.show { |
|||
z-index: 9901; |
|||
opacity: 1; |
|||
} |
|||
|
|||
.cropper-head { |
|||
position: fixed; |
|||
top: 0; |
|||
width: 750rpx; |
|||
z-index: 6; |
|||
height: calc(var(--status-bar-height) + 88rpx); |
|||
padding-top: var(--status-bar-height); |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
align-items: center; |
|||
} |
|||
|
|||
.cropper-btn { |
|||
height: 64rpx; |
|||
margin: 0 20rpx; |
|||
padding: 0 30rpx; |
|||
line-height: 64rpx; |
|||
color: #fff; |
|||
font-size: 26rpx; |
|||
} |
|||
|
|||
.cropper-body { |
|||
margin: calc(var(--status-bar-height) + 88rpx) 30rpx 0 30rpx; |
|||
height: calc(100vh - var(--status-bar-height) - 88rpx - 100rpx - var(--safe-area-inset-bottom)); |
|||
position: relative; |
|||
} |
|||
|
|||
.cropper-bottom { |
|||
height: calc(var(--safe-area-inset-bottom) + 100rpx); |
|||
padding-top: var(--safe-area-inset-bottom); |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
position: fixed; |
|||
z-index: 6; |
|||
width: 750rpx; |
|||
bottom: 0; |
|||
} |
|||
|
|||
.cropper-ok { |
|||
color: #39f; |
|||
} |
|||
|
|||
// 此属性导致Android显示异常 |
|||
.cropper-image { |
|||
position: absolute; |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.cropper-stage { |
|||
position: absolute; |
|||
|
|||
.cropper-box { |
|||
position: absolute; |
|||
border: 4rpx solid #ddd; |
|||
box-sizing: border-box; |
|||
box-shadow: 0 0 0 2000rpx rgba(0, 0, 0, 0.5); |
|||
|
|||
.lt { |
|||
position: absolute; |
|||
height: 48rpx; |
|||
width: 48rpx; |
|||
left: -6rpx; |
|||
top: -6rpx; |
|||
border-left: 12rpx solid #ffffff; |
|||
border-top: 12rpx solid #ffffff; |
|||
} |
|||
|
|||
.lb { |
|||
position: absolute; |
|||
height: 48rpx; |
|||
width: 48rpx; |
|||
left: -6rpx; |
|||
bottom: -6rpx; |
|||
border-left: 12rpx solid #ffffff; |
|||
border-bottom: 12rpx solid #ffffff; |
|||
} |
|||
|
|||
.rt { |
|||
position: absolute; |
|||
height: 48rpx; |
|||
width: 48rpx; |
|||
right: -6rpx; |
|||
top: -6rpx; |
|||
border-right: 12rpx solid #ffffff; |
|||
border-top: 12rpx solid #ffffff; |
|||
} |
|||
|
|||
.rb { |
|||
position: absolute; |
|||
height: 48rpx; |
|||
width: 48rpx; |
|||
right: -6rpx; |
|||
bottom: -6rpx; |
|||
border-right: 12rpx solid #ffffff; |
|||
border-bottom: 12rpx solid #ffffff; |
|||
} |
|||
|
|||
.line-v, |
|||
.line-h { |
|||
position: absolute; |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
.line-v { |
|||
width: 2rpx; |
|||
border-left: 2rpx dashed #fff; |
|||
height: 100%; |
|||
} |
|||
|
|||
.line-h { |
|||
height: 2rpx; |
|||
border-bottom: 2rpx dashed #fff; |
|||
width: 100%; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.cropper-canvas { |
|||
position: fixed; |
|||
background-color: red; |
|||
left: 5000rpx; |
|||
} |
|||
} |
|||
|
|||
// 安全域兼容样式 |
|||
page { |
|||
--safe-area-inset-top: 0px; |
|||
--safe-area-inset-right: 0px; |
|||
--safe-area-inset-bottom: 0px; |
|||
--safe-area-inset-left: 0px; |
|||
|
|||
@supports (top: constant(safe-area-inset-top)) { |
|||
--safe-area-inset-top: constant(safe-area-inset-top); |
|||
--safe-area-inset-right: constant(safe-area-inset-right); |
|||
--safe-area-inset-bottom: constant(safe-area-inset-bottom); |
|||
--safe-area-inset-left: constant(safe-area-inset-left); |
|||
} |
|||
|
|||
@supports (top: env(safe-area-inset-top)) { |
|||
--safe-area-inset-top: env(safe-area-inset-top); |
|||
--safe-area-inset-right: env(safe-area-inset-right); |
|||
//--safe-area-inset-bottom: 12px; |
|||
--safe-area-inset-bottom: env(safe-area-inset-bottom); |
|||
--safe-area-inset-left: env(safe-area-inset-left); |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,79 @@ |
|||
#### 【注意】由于赶时间,只做了功能性测试,兼容性测试暂时无法进行,更不用说优化测试了。欢迎有遇到问题的留言,或者自己动手修改也可以。 |
|||
#### 【注意】因为属于业余时间开发的一个项目所依赖的插件,所以更新较缓慢。 |
|||
|
|||
### 使用方式 |
|||
|
|||
```javascript |
|||
import Cropper from '@/components/cropper/cropper.vue'; |
|||
``` |
|||
|
|||
```html |
|||
<cropper :imagePath="originalFaceSrc" :aspectRatio="1" @complete="complete" @cancel="cancel" v-if="handleFaceStatus"></cropper> |
|||
``` |
|||
|
|||
使用v-if控制显示,mounted内执行init方法,使用简单,操作便利。 |
|||
|
|||
## 参数 |
|||
| 参数 | 类型 | 解释 | 默认 | 必填 | 备注 | |
|||
| ------------ | ------------ | ------------ | ------------ | ------------ | ------------ | |
|||
| quality | Number | 返回图片质量 | 1 | 否 | 如返回的图片仅需预览不用上传,请适当调低 | |
|||
| imagePath | String | 原始图片路径 |"" | 是 | blob、静态图片资源地址或者base64(未测试) | |
|||
| outputFileType | String | 目标文件的类型 | jpg | 否 | jpg:输出jpg格式图片;png:输出png格式图片 | |
|||
| aspectRatio | Number, null | 目标图片的宽高比 | null | 否 | null,即不限制剪裁宽高比。aspectRatio需大于0 | |
|||
| minBoxWidthRatio | Number | 最小剪裁尺寸与原图尺寸的比率(宽)| 0.15 | 否 | 宽度最小剪裁到原图的0.15宽 | |
|||
| minBoxHeightRatio | Number | 最小剪裁尺寸与原图尺寸的比率(高)| 0.15 | 否 | 当设置aspectRatio时,minBoxHeight值设置无效。minBoxHeight值由minBoxWidth 和 aspectRatio自动计算得到 | |
|||
| initialBoxWidthRatio | Number | 剪裁框初始大小比率(宽) | 0.8 | 否 | 裁框默认宽度为图片宽度的0.8倍 | |
|||
| initialBoxHeightRatio| Number | 剪裁框初始大小比率(高) | 0.8 | 否 | 当设置aspectRatio时,initialBoxHeightRatio值设置无效。initialBoxHeightRatio值由initialBoxWidthRatio 和 aspectRatio自动计算得到| |
|||
| saveMode | String | 保存模式 | "None" | 否 | None(无限制)ScaleTo500(缩放到指定的尺寸 如 500宽度) Max500(不超过500宽度) | |
|||
## 事件 |
|||
|
|||
| 事件名 | 解释 | 参数 | 备注 | |
|||
| ------------ | ------------ | ------------ | ------------ | |
|||
| reset | 用户点击重做后触发 | 无 | 可能无用,但留一个给开发者备用 | |
|||
| cancel | 用户点击取消后触发 | 无 | 开发者可以将组件置为消失v-if(尽量不要用v-show) | |
|||
| complete | 用户点击裁剪后触发 | path:路径,h5为base64,app与小程序为临时路径;param:裁切参数,见【备注1】 | 核心功能 | |
|||
|
|||
|
|||
### 备注1 |
|||
param: |
|||
```json |
|||
{ |
|||
x:Number, //裁切左上角X坐标 |
|||
y:Number, //裁切左上角Y坐标 |
|||
width:Number, //裁切宽度 |
|||
height:Number, //裁切高度 |
|||
rotate:Number, //旋转(未实现,预留) |
|||
scaleX:Number, //X轴缩放(未实现,预留) |
|||
scaleY:Number, //Y轴缩放(未实现,预留) |
|||
} |
|||
``` |
|||
|
|||
### 备注2 |
|||
本组件样式支持iphone安全范围但需要手动开启 |
|||
请在插件被样式中最后备注的地方解除备注即可。 |
|||
控制台会报错,无视即可。 |
|||
或者复制如下样式样式代码 |
|||
```css |
|||
// 安全域兼容样式 |
|||
page { |
|||
--safe-area-inset-top: 0px; |
|||
--safe-area-inset-right: 0px; |
|||
--safe-area-inset-bottom: 0px; |
|||
--safe-area-inset-left: 0px; |
|||
|
|||
@supports (top: constant(safe-area-inset-top)) { |
|||
--safe-area-inset-top: constant(safe-area-inset-top); |
|||
--safe-area-inset-right: constant(safe-area-inset-right); |
|||
--safe-area-inset-bottom: constant(safe-area-inset-bottom); |
|||
--safe-area-inset-left: constant(safe-area-inset-left); |
|||
} |
|||
|
|||
@supports (top: env(safe-area-inset-top)) { |
|||
--safe-area-inset-top: env(safe-area-inset-top); |
|||
--safe-area-inset-right: env(safe-area-inset-right); |
|||
//--safe-area-inset-bottom: 12px; |
|||
--safe-area-inset-bottom: env(safe-area-inset-bottom); |
|||
--safe-area-inset-left: env(safe-area-inset-left); |
|||
} |
|||
} |
|||
``` |
@ -0,0 +1,163 @@ |
|||
<template> |
|||
|
|||
<view :style="{'height':navHeightPx,'display':'flex','flex-direction':'row','align-items':'center'}" |
|||
v-if="btnType!=0"> |
|||
|
|||
<view class="uni-combox__input-box"> |
|||
|
|||
<image v-if="btnType==2 && filterCandidatesLength > 0" :style="{'height':navHeightPx,'width':navHeightPx}" |
|||
mode="aspectFit" :src="btnSource" @click.stop="toggleSelector"> |
|||
</image> |
|||
<text v-if="btnType==1 && filterCandidatesLength > 0" |
|||
:style="{'height':navHeightPx,'line-height':navHeightPx,'font-size':'28rpx','color':'#FFFFFF','padding-left':'20rpx','padding-right':'20rpx'}" |
|||
@click.stop="toggleSelector">{{btnSource}}</text> |
|||
|
|||
<view> |
|||
|
|||
<view class="uni-combox__selector" v-if="showSelector" :style="{'margin-left': offsetPx}"> |
|||
<scroll-view scroll-y="true" class="uni-combox__selector-scroll"> |
|||
<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0"> |
|||
<text>{{emptyTips}}</text> |
|||
</view> |
|||
<view class="uni-combox__selector-item" v-for="(item,index) in candidates" :key="index" |
|||
@click="onSelectorClick(index)"> |
|||
<image :src="item.src" class="modal-img"></image> |
|||
<text |
|||
style="display:-webkit-box;-webkit-box-orient:vertical; -webkit-line-clamp:1; overflow:hidden;">{{item.name}}</text> |
|||
</view> |
|||
</scroll-view> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'uni-combox', |
|||
props: { |
|||
btnType: { |
|||
type: String, |
|||
// 0不使用 1使用文字 2使用图片 |
|||
default: "0" |
|||
}, |
|||
btnSource: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
emptyTips: { |
|||
type: String, |
|||
default: '无' |
|||
}, |
|||
candidates: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
showSelector: false, |
|||
navHeightPx: "", |
|||
navWidth: "", |
|||
windowWidth: "", |
|||
offsetPx: "" |
|||
} |
|||
}, |
|||
computed: { |
|||
filterCandidatesLength() { |
|||
return this.candidates.length; |
|||
} |
|||
}, |
|||
methods: { |
|||
toggleSelector() { |
|||
this.showSelector = !this.showSelector |
|||
}, |
|||
onSelectorClick(index) { |
|||
this.showSelector = false |
|||
this.$emit('onselect', index, this.candidates) |
|||
}, |
|||
closeDropItem() { |
|||
this.showSelector = false |
|||
} |
|||
}, |
|||
created() { |
|||
this.navHeightPx = getApp().globalData.navHeightPx |
|||
this.navWidth = getApp().globalData.navWidth |
|||
this.windowWidth = getApp().globalData.windowWidth |
|||
let offset = this.windowWidth - this.navWidth; |
|||
this.offsetPx = "-160rpx" |
|||
// 获取胶囊高度 |
|||
// #ifdef MP-WEIXIN||MP-BAID||MP-QQ||MP-TOUTIAO |
|||
this.offsetPx = "-" + (this.windowWidth - this.navWidth - 20) + "px" |
|||
// #endif |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.uni-combox__input-box { |
|||
position: relative; |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
} |
|||
|
|||
.uni-combox__selector { |
|||
box-sizing: border-box; |
|||
position: absolute; |
|||
top: 42px; |
|||
left: 0; |
|||
width: 100%; |
|||
background-color: #FFFFFF; |
|||
border-radius: 6px; |
|||
box-shadow: #DDDDDD 4px 4px 8px, #DDDDDD -4px -4px 8px; |
|||
z-index: 2; |
|||
width: 240rpx; |
|||
} |
|||
|
|||
.uni-combox__selector-scroll { |
|||
max-height: 200px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.uni-combox__selector::before { |
|||
content: ''; |
|||
position: absolute; |
|||
width: 0; |
|||
height: 0; |
|||
border-bottom: solid 6px #FFFFFF; |
|||
border-right: solid 6px transparent; |
|||
border-left: solid 6px transparent; |
|||
left: 85%; |
|||
top: -6px; |
|||
margin-left: -6px; |
|||
} |
|||
|
|||
.uni-combox__selector-empty, |
|||
.uni-combox__selector-item { |
|||
display: flex; |
|||
line-height: 80rpx; |
|||
font-size: 14px; |
|||
text-align: center; |
|||
border-bottom: solid 1px #DDDDDD; |
|||
margin: 0px 10px; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.uni-combox__selector-empty:last-child, |
|||
.uni-combox__selector-item:last-child { |
|||
border-bottom: none; |
|||
} |
|||
|
|||
.modal-img { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
margin-right: 5rpx; |
|||
} |
|||
</style> |
Binary file not shown.
@ -0,0 +1,379 @@ |
|||
<template> |
|||
<view class="fui-button__wrap" |
|||
:style="{width: width,height: height,marginTop:margin[0] || 0, marginRight:margin[1]||0,marginBottom:margin[2] || margin[0]||0,marginLeft:margin[3] || margin[1]||0,borderRadius: radius}" |
|||
@touchstart="handleStart" @touchend="handleClick" @touchcancel="handleEnd" @tap="handleTap"> |
|||
<button class="fui-button" :class="[ |
|||
bold ? 'fui-text__bold' : '', |
|||
time && (plain || type==='link') ? 'fui-button__opacity' : '', |
|||
disabled && !disabledBackground ? 'fui-button__opacity' : '', |
|||
!background && !disabledBackground && !plain?('fui-button__'+type):'', |
|||
!width || width==='100%' || width==='true'?'fui-button__flex-1':'', |
|||
time && !plain && type!=='link' ? 'fui-button__active' : '' |
|||
]" :style="{ |
|||
width: width, |
|||
height: height, |
|||
lineHeight: height, |
|||
background: disabled && disabledBackground ? disabledBackground : (plain ? 'transparent' : background), |
|||
borderWidth:borderWidth, |
|||
borderColor: borderColor ? borderColor : disabled && disabledBackground ? disabledBackground : (background || 'transparent'), |
|||
borderRadius: radius, |
|||
fontSize: size + 'rpx', |
|||
color: disabled && disabledBackground ? disabledColor : color |
|||
}" :loading="loading" :form-type="formType" :open-type="openType" @getuserinfo="bindgetuserinfo" |
|||
@getphonenumber="bindgetphonenumber" @contact="bindcontact" @error="binderror" |
|||
@opensetting="bindopensetting" :disabled="disabled" :scope="scope"> |
|||
<text class="fui-button__text" |
|||
:class="{'fui-btn__gray-color':!background && !disabledBackground && !plain && type==='gray' && color==='#fff','fui-text__bold':bold}" |
|||
v-if="text" |
|||
:style="{fontSize: size + 'rpx',lineHeight:size + 'rpx',color: disabled && disabledBackground ? disabledColor : color}">{{text}}</text> |
|||
<slot></slot> |
|||
</button> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'fui-button', |
|||
emits: ['click', 'getuserinfo', 'contact', 'getphonenumber', 'error', 'opensetting'], |
|||
// #ifndef VUE3 |
|||
// #ifdef MP-WEIXIN |
|||
behaviors: ['wx://form-field-button'], |
|||
// #endif |
|||
// #endif |
|||
props: { |
|||
//样式类型:primary,success, warning,danger,link,purple,gray |
|||
type: { |
|||
type: String, |
|||
default: 'primary' |
|||
}, |
|||
//按钮背景色,当传入值时type失效 |
|||
background: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
//按钮显示文本 |
|||
text: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
//按钮字体颜色 |
|||
color: { |
|||
type: String, |
|||
default: '#fff' |
|||
}, |
|||
//按钮禁用背景色 |
|||
disabledBackground: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
//按钮禁用字体颜色 |
|||
disabledColor: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
borderWidth: { |
|||
type: String, |
|||
// #ifdef APP-NVUE |
|||
default: '0.5px' |
|||
// #endif |
|||
// #ifndef APP-NVUE |
|||
default: '1rpx' |
|||
// #endif |
|||
}, |
|||
borderColor: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
//宽度 |
|||
width: { |
|||
type: String, |
|||
default: '100%' |
|||
}, |
|||
//高度 |
|||
height: { |
|||
type: String, |
|||
default: '96rpx' |
|||
}, |
|||
//字体大小,单位rpx |
|||
size: { |
|||
type: [Number, String], |
|||
default: 32 |
|||
}, |
|||
bold: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
//['20rpx','30rpx','20rpx','30rpx']->[上,右,下,左] |
|||
margin: { |
|||
type: Array, |
|||
default () { |
|||
return ['0', '0'] |
|||
} |
|||
}, |
|||
//圆角 |
|||
radius: { |
|||
type: String, |
|||
default: '16rpx' |
|||
}, |
|||
plain: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
loading: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
formType: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
openType: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
//支付宝小程序 |
|||
//当 open-type 为 getAuthorize 时,可以设置 scope 为:phoneNumber、userInfo |
|||
scope: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
index: { |
|||
type: [Number, String], |
|||
default: 0 |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
time: 0, |
|||
trigger: false, |
|||
tap: false |
|||
}; |
|||
}, |
|||
methods: { |
|||
handleStart() { |
|||
if (this.disabled) return; |
|||
this.trigger = false; |
|||
this.tap = true; |
|||
if (new Date().getTime() - this.time <= 150) return; |
|||
this.trigger = true; |
|||
this.time = new Date().getTime(); |
|||
}, |
|||
handleClick() { |
|||
if (this.disabled || !this.trigger) return; |
|||
//如果想取消操作,比如长按再放开,这里可以做时间判断 |
|||
this.time = 0; |
|||
this.$emit('click', { |
|||
index: Number(this.index) |
|||
}); |
|||
}, |
|||
handleTap() { |
|||
// #ifdef H5 |
|||
if (this.disabled || this.tap) return; |
|||
this.$emit('click', { |
|||
index: Number(this.index) |
|||
}); |
|||
// #endif |
|||
}, |
|||
handleEnd() { |
|||
if (this.disabled) return; |
|||
setTimeout(() => { |
|||
this.time = 0; |
|||
}, 150); |
|||
}, |
|||
bindgetuserinfo({ |
|||
detail = {} |
|||
} = {}) { |
|||
this.$emit('getuserinfo', detail); |
|||
}, |
|||
bindcontact({ |
|||
detail = {} |
|||
} = {}) { |
|||
this.$emit('contact', detail); |
|||
}, |
|||
bindgetphonenumber({ |
|||
detail = {} |
|||
} = {}) { |
|||
this.$emit('getphonenumber', detail); |
|||
}, |
|||
binderror({ |
|||
detail = {} |
|||
} = {}) { |
|||
this.$emit('error', detail); |
|||
}, |
|||
bindopensetting({ |
|||
detail = {} |
|||
} = {}) { |
|||
this.$emit('opensetting', detail); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.fui-button__wrap { |
|||
position: relative; |
|||
} |
|||
|
|||
.fui-button { |
|||
/* #ifdef APP-NVUE */ |
|||
border-width: 0.5px; |
|||
/* #endif */ |
|||
/* #ifndef APP-NVUE */ |
|||
border-width: 1rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-sizing: border-box; |
|||
/* #endif */ |
|||
border-style: solid; |
|||
position: relative; |
|||
padding-left: 0; |
|||
padding-right: 0; |
|||
overflow: hidden; |
|||
/* #ifndef APP-NVUE */ |
|||
transform: translateZ(0); |
|||
-webkit-touch-callout: none; |
|||
-webkit-user-select: none; |
|||
user-select: none; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-button__flex-1 { |
|||
flex: 1; |
|||
/* #ifndef APP-NVUE */ |
|||
width: 100%; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-button::after { |
|||
border: 0; |
|||
} |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
.fui-button__active { |
|||
overflow: hidden !important; |
|||
} |
|||
|
|||
.fui-button__active::after { |
|||
content: ' '; |
|||
background-color: var(--fui-bg-color-hover, rgba(0, 0, 0, 0.2)); |
|||
position: absolute; |
|||
width: 100%; |
|||
height: 100%; |
|||
left: 0; |
|||
right: 0; |
|||
top: 0; |
|||
transform: none; |
|||
z-index: 1; |
|||
border-radius: 0; |
|||
} |
|||
|
|||
/* #endif */ |
|||
.fui-button__text { |
|||
text-align: center; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: center !important; |
|||
padding-left: 0 !important; |
|||
|
|||
} |
|||
|
|||
.fui-button__opacity { |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
.fui-text__bold { |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.fui-button__link { |
|||
border-color: transparent !important; |
|||
background: transparent !important; |
|||
} |
|||
|
|||
.fui-button__primary { |
|||
/* #ifdef APP-NVUE */ |
|||
border-color: #465CFF !important; |
|||
background: #465CFF !important; |
|||
/* #endif */ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
border-color: var(--fui-color-primary, #465CFF) !important; |
|||
background: var(--fui-color-primary, #465CFF) !important; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-button__success { |
|||
/* #ifdef APP-NVUE */ |
|||
border-color: #09BE4F !important; |
|||
background: #09BE4F !important; |
|||
/* #endif */ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
border-color: var(--fui-color-success, #09BE4F) !important; |
|||
background: var(--fui-color-success, #09BE4F) !important; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-button__warning { |
|||
/* #ifdef APP-NVUE */ |
|||
border-color: #FFB703 !important; |
|||
background: #FFB703 !important; |
|||
/* #endif */ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
border-color: var(--fui-color-warning, #FFB703) !important; |
|||
background: var(--fui-color-warning, #FFB703) !important; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-button__danger { |
|||
/* #ifdef APP-NVUE */ |
|||
border-color: #FF2B2B !important; |
|||
background: #FF2B2B !important; |
|||
/* #endif */ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
border-color: var(--fui-color-danger, #FF2B2B) !important; |
|||
background: var(--fui-color-danger, #FF2B2B) !important; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-button__purple { |
|||
/* #ifdef APP-NVUE */ |
|||
border-color: #6831FF !important; |
|||
background: #6831FF !important; |
|||
/* #endif */ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
border-color: var(--fui-color-purple, #6831FF) !important; |
|||
background: var(--fui-color-purple, #6831FF) !important; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-button__gray { |
|||
/* #ifdef APP-NVUE */ |
|||
border-color: #F8F8F8 !important; |
|||
background: #F8F8F8 !important; |
|||
/* #endif */ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
border-color: var(--fui-bg-color-content, #F8F8F8) !important; |
|||
background: var(--fui-bg-color-content, #F8F8F8) !important; |
|||
color: var(--fui-color-primary, #465CFF) !important; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-btn__gray-color { |
|||
/* #ifdef APP-NVUE */ |
|||
color: #465CFF !important; |
|||
/* #endif */ |
|||
/* #ifndef APP-NVUE */ |
|||
color: var(--fui-color-primary, #465CFF) !important; |
|||
/* #endif */ |
|||
} |
|||
</style> |
@ -0,0 +1,445 @@ |
|||
<template> |
|||
<view class="fui-dropdown__menu"> |
|||
<slot></slot> |
|||
<view class="fui-dropdown__menu-list" |
|||
:class="{'fui-ddm__down':direction!=='up','fui-ddm__up':direction==='up','fui-ddm__down-show':isShow && direction!=='up','fui-ddm__up-show':isShow && direction==='up'}" |
|||
:style="getStyles"> |
|||
<scroll-view class="fui-ddm__scroll" scroll-y :style="{maxHeight:maxHeight+'rpx',minWidth:minWidth+'rpx'}"> |
|||
<view class="fui-dropdown__menu-item" :style="{background:background,padding:padding}" |
|||
:class="{'fui-ddm__reverse':isReverse,'fui-ddm__item-line':splitLine && itemList.length-1!==index}" |
|||
v-for="(model,index) in itemList" :key="index" @tap.stop="itemClick(index)"> |
|||
<view class="fui-ddm__checkbox" |
|||
:class="{'fui-is__checkmark':isCheckMark,'fui-ddm__checkbox-color':(!checkboxColor || checkboxColor=='true') && model.checked && !isCheckMark}" |
|||
:style="{background:model.checked && !isCheckMark ?checkboxColor:'transparent',borderColor:model.checked && !isCheckMark ?checkboxColor:borderColor}" |
|||
v-if="isCheckbox"> |
|||
<view class="fui-ddm__checkmark" |
|||
:style="{borderBottomColor:checkmarkColor,borderRightColor:checkmarkColor}" |
|||
v-if="model.checked"></view> |
|||
</view> |
|||
<view class="fui-ddm__flex"> |
|||
<view class="fui-ddm__icon-box" |
|||
:class="{'fui-ddm__icon-ml':!isReverse && isCheckbox,'fui-ddm__icon-mr':isReverse}" |
|||
:style="{width:iconWidth+'rpx',height:iconWidth+'rpx'}" v-if="model.src"> |
|||
<image src="/static/images/common/logo.png" |
|||
:style="{width:iconWidth+'rpx',height:iconWidth+'rpx'}"></image> |
|||
</view> |
|||
<text class="fui-ddm__item-text" |
|||
:class="{'fui-ddm__text-pl':!isReverse && (isCheckbox || model.src),'fui-ddm__text-pr':isReverse && (isCheckbox || model.src)}" |
|||
:style="{fontSize:size+'rpx',color:selectedColor && model.checked?selectedColor:color}">{{model.text}}</text> |
|||
</view> |
|||
</view> |
|||
</scroll-view> |
|||
</view> |
|||
<view class="fui-ddm__mask" :style="{backgrround:maskBackground}" v-if="isShow && isMask" @tap="close(1)"> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
/*! |
|||
* 下拉菜单 |
|||
* 由于weex android上overflow仅支持hidden,所以该组件不支持Nvue端 |
|||
* Nvue端使用组件fui-dropdown-list或fui-select组件替代该组件 |
|||
*/ |
|||
export default { |
|||
name: "fui-dropdown-menu", |
|||
emits: ['click', 'close'], |
|||
props: { |
|||
options: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
maxHeight: { |
|||
type: [Number, String], |
|||
default: 400 |
|||
}, |
|||
minWidth: { |
|||
type: [Number, String], |
|||
default: 0 |
|||
}, |
|||
left: { |
|||
type: [Number, String], |
|||
default: 0 |
|||
}, |
|||
//right大于等于0时生效,left失效 |
|||
right: { |
|||
type: [Number, String], |
|||
default: -1 |
|||
}, |
|||
background: { |
|||
type: String, |
|||
default: '#fff' |
|||
}, |
|||
radius: { |
|||
type: [Number, String], |
|||
default: 0 |
|||
}, |
|||
padding: { |
|||
type: String, |
|||
default: '32rpx' |
|||
}, |
|||
isCheckbox: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
//选择框选中后颜色 |
|||
checkboxColor: { |
|||
type: String, |
|||
// #ifdef APP-NVUE |
|||
default: '#465CFF' |
|||
// #endif |
|||
// #ifndef APP-NVUE |
|||
default: '' |
|||
// #endif |
|||
}, |
|||
//选择框未选中时边框颜色 |
|||
borderColor: { |
|||
type: String, |
|||
default: '#ccc' |
|||
}, |
|||
//是否只展示对号,无边框背景 |
|||
isCheckMark: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
//对号颜色 |
|||
checkmarkColor: { |
|||
type: String, |
|||
default: '#fff' |
|||
}, |
|||
//选择框与内容是否颠倒排列 |
|||
isReverse: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
//是否需要分割线条 |
|||
splitLine: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
iconWidth: { |
|||
type: [Number, String], |
|||
default: 48 |
|||
}, |
|||
size: { |
|||
type: [Number, String], |
|||
default: 32 |
|||
}, |
|||
color: { |
|||
type: String, |
|||
default: '#181818' |
|||
}, |
|||
selectedColor: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
isMask: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
maskBackground: { |
|||
type: String, |
|||
default: 'transparent' |
|||
}, |
|||
//down/up |
|||
direction: { |
|||
type: String, |
|||
default: 'down' |
|||
} |
|||
}, |
|||
watch: { |
|||
options(newVal) { |
|||
this.initData(newVal) |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
itemList: [], |
|||
isShow: false |
|||
}; |
|||
}, |
|||
computed: { |
|||
getStyles() { |
|||
let styles = `border-radius:${this.radius}rpx;background:${this.background};` |
|||
let right = Number(this.right || 0) |
|||
if (right >= 0) { |
|||
styles += 'right:0;' |
|||
} else { |
|||
styles += 'left:0;' |
|||
} |
|||
return styles |
|||
} |
|||
}, |
|||
created() { |
|||
this.initData(this.options) |
|||
}, |
|||
methods: { |
|||
initData(vals) { |
|||
if (vals && vals.length > 0) { |
|||
if (typeof vals[0] !== 'object') { |
|||
vals = vals.map(item => { |
|||
return { |
|||
text: item, |
|||
checked: false |
|||
} |
|||
}) |
|||
} else { |
|||
vals.map(item => { |
|||
item.checked = item.checked || false |
|||
}) |
|||
} |
|||
this.itemList = vals; |
|||
} |
|||
}, |
|||
itemClick(index) { |
|||
let item = this.itemList[index] |
|||
let vals = [...this.itemList] |
|||
vals.forEach((item, idx) => { |
|||
if (index === idx) { |
|||
item.checked = true |
|||
} else { |
|||
item.checked = false |
|||
} |
|||
}) |
|||
this.itemList = vals; |
|||
this.$emit('click', { |
|||
index: index, |
|||
...item |
|||
}) |
|||
this.close(2) |
|||
}, |
|||
close(type) { |
|||
this.isShow = false; |
|||
if (type === 1) { |
|||
this.$emit('close', {}) |
|||
} |
|||
}, |
|||
show() { |
|||
this.isShow = true; |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.fui-dropdown__menu { |
|||
flex: 1; |
|||
position: relative; |
|||
} |
|||
|
|||
.fui-ddm__scroll { |
|||
/* #ifndef APP-NVUE */ |
|||
width: auto; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-dropdown__menu-list { |
|||
position: absolute; |
|||
box-shadow: 0 0 10rpx rgba(2, 4, 38, 0.05); |
|||
overflow: hidden; |
|||
z-index: 992; |
|||
opacity: 0; |
|||
/* #ifndef APP-NVUE */ |
|||
visibility: hidden; |
|||
transition: all 0.3s ease-in-out; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-ddm__down { |
|||
transform-origin: 0 0; |
|||
bottom: 0; |
|||
/* #ifndef APP-NVUE */ |
|||
transform: translate3d(0, 100%, 0) scaleY(0); |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-ddm__down-show { |
|||
/* #ifndef APP-NVUE */ |
|||
transform: translate3d(0, 100%, 0) scaleY(1); |
|||
visibility: visible; |
|||
/* #endif */ |
|||
opacity: 1; |
|||
} |
|||
|
|||
.fui-ddm__up { |
|||
transform-origin: 0 100%; |
|||
top: 0; |
|||
/* #ifndef APP-NVUE */ |
|||
transform: translate3d(0, -100%, 0) scaleY(0); |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-ddm__up-show { |
|||
/* #ifndef APP-NVUE */ |
|||
transform: translate3d(0, -100%, 0) scaleY(1); |
|||
visibility: visible; |
|||
/* #endif */ |
|||
opacity: 1; |
|||
} |
|||
|
|||
.fui-ddm__mask { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
z-index: 990; |
|||
} |
|||
|
|||
.fui-dropdown__menu-item { |
|||
/* #ifndef APP-NVUE */ |
|||
width: 100%; |
|||
display: flex; |
|||
box-sizing: border-box; |
|||
transform: scale(1) translateZ(0); |
|||
/* #endif */ |
|||
flex: 1; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
background-color: #FFFFFF; |
|||
position: relative; |
|||
/* #ifdef H5 */ |
|||
cursor: pointer; |
|||
/* #endif */ |
|||
|
|||
} |
|||
|
|||
.fui-ddm__flex { |
|||
/* #ifndef APP-NVUE */ |
|||
width: 100%; |
|||
display: flex; |
|||
box-sizing: border-box; |
|||
/* #endif */ |
|||
flex: 1; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
} |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
.fui-ddm__item-line { |
|||
position: relative; |
|||
} |
|||
|
|||
.fui-ddm__item-line::after { |
|||
content: ''; |
|||
position: absolute; |
|||
border-bottom: 1px solid var(--fui-color-border, #EEEEEE); |
|||
/* #ifdef H5 */ |
|||
transform: scaleY(0.5); |
|||
/* #endif */ |
|||
|
|||
/* #ifndef H5 */ |
|||
transform: scaleY(0.5) translateZ(0); |
|||
/* #endif */ |
|||
transform-origin: 0 100%; |
|||
bottom: 0; |
|||
right: 0; |
|||
left: 32rpx; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
/* #endif */ |
|||
|
|||
.fui-dropdown__menu-item:active { |
|||
/* #ifdef APP-NVUE */ |
|||
background-color: rgba(0, 0, 0, .2) !important; |
|||
/* #endif */ |
|||
/* #ifndef APP-NVUE */ |
|||
background-color: var(--fui-bg-color-hover, rgba(0, 0, 0, .2)) !important; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-ddm__reverse { |
|||
justify-content: space-between; |
|||
flex-direction: row-reverse; |
|||
} |
|||
|
|||
.fui-ddm__checkbox { |
|||
font-size: 0; |
|||
color: rgba(0, 0, 0, 0); |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
border-width: 1px; |
|||
border-style: solid; |
|||
/* #ifdef APP-NVUE */ |
|||
border-radius: 40rpx; |
|||
/* #endif */ |
|||
/* #ifndef APP-NVUE */ |
|||
display: inline-flex; |
|||
box-sizing: border-box; |
|||
border-radius: 50%; |
|||
vertical-align: top; |
|||
flex-shrink: 0; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: center; |
|||
overflow: hidden; |
|||
position: relative; |
|||
} |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
.fui-ddm__checkbox-color { |
|||
background: var(--fui-color-primary, #465CFF) !important; |
|||
border-color: var(--fui-color-primary, #465CFF) !important; |
|||
} |
|||
|
|||
/* #endif */ |
|||
|
|||
.fui-is__checkmark { |
|||
border-width: 0 !important; |
|||
background: transparent !important; |
|||
} |
|||
|
|||
.fui-ddm__checkmark { |
|||
width: 20rpx; |
|||
height: 40rpx; |
|||
border-bottom-style: solid; |
|||
border-bottom-width: 3px; |
|||
border-bottom-color: #FFFFFF; |
|||
border-right-style: solid; |
|||
border-right-width: 3px; |
|||
border-right-color: #FFFFFF; |
|||
/* #ifndef APP-NVUE */ |
|||
box-sizing: border-box; |
|||
transform: rotate(45deg) scale(0.5) translateZ(0); |
|||
/* #endif */ |
|||
/* #ifdef APP-NVUE */ |
|||
transform: rotate(45deg) scale(0.5); |
|||
/* #endif */ |
|||
transform-origin: 54% 48%; |
|||
} |
|||
|
|||
.fui-ddm__item-text { |
|||
/* #ifndef APP-NVUE */ |
|||
word-break: break-all; |
|||
/* #endif */ |
|||
font-weight: normal; |
|||
} |
|||
|
|||
.fui-ddm__text-pl { |
|||
padding-left: 24rpx; |
|||
} |
|||
|
|||
.fui-ddm__text-pr { |
|||
padding-right: 24rpx; |
|||
} |
|||
|
|||
.fui-ddm__icon-box { |
|||
overflow: hidden; |
|||
background-color: #F1F4FA; |
|||
/* #ifndef APP-NVUE */ |
|||
flex-shrink: 0; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-ddm__icon-ml { |
|||
margin-left: 24rpx; |
|||
} |
|||
|
|||
.fui-ddm__icon-mr { |
|||
margin-right: 24rpx; |
|||
} |
|||
</style> |
@ -0,0 +1,154 @@ |
|||
export default { |
|||
"addressbook":"\ue80c", |
|||
"addfriends-fill": "\ue80a", |
|||
"addfriends": "\ue80b", |
|||
"backspace-fill": "\ue808", |
|||
"backspace": "\ue809", |
|||
"bankcard-fill": "\ue806", |
|||
"bankcard": "\ue807", |
|||
"camera-fill": "\ue804", |
|||
"camera": "\ue805", |
|||
"captcha-fill": "\ue802", |
|||
"captcha": "\ue803", |
|||
"cart-fill": "\ue800", |
|||
"cart": "\ue801", |
|||
"classify": "\ue7fe", |
|||
"classify-fill": "\ue7ff", |
|||
"comment-fill": "\ue7fc", |
|||
"comment": "\ue7fd", |
|||
"community-fill": "\ue7fa", |
|||
"community": "\ue7fb", |
|||
"coupon-fill": "\ue7f8", |
|||
"coupon": "\ue7f9", |
|||
"delete": "\ue7f6", |
|||
"delete-fill": "\ue7f7", |
|||
"edit": "\ue7f4", |
|||
"edit-fill": "\ue7f5", |
|||
"fabulous-fill": "\ue7f2", |
|||
"fabulous": "\ue7f3", |
|||
"find": "\ue7f0", |
|||
"find-fill": "\ue7f1", |
|||
"help-fill": "\ue7ee", |
|||
"help": "\ue7ef", |
|||
"home-fill": "\ue7ec", |
|||
"home": "\ue7ed", |
|||
"idcard-fill": "\ue7ea", |
|||
"idcard": "\ue7eb", |
|||
"info": "\ue7e8", |
|||
"info-fill": "\ue7e9", |
|||
"invite-fill": "\ue7e6", |
|||
"invite": "\ue7e7", |
|||
"kefu-fill": "\ue7e4", |
|||
"kefu": "\ue7e5", |
|||
"like-fill": "\ue7e2", |
|||
"like": "\ue7e3", |
|||
"location": "\ue7e0", |
|||
"location-fill": "\ue7e1", |
|||
"lock": "\ue7de", |
|||
"lock-fill": "\ue7df", |
|||
"mail-fill": "\ue7dc", |
|||
"mail": "\ue7dd", |
|||
"message": "\ue7da", |
|||
"message-fill": "\ue7db", |
|||
"mobile-fill": "\ue7d8", |
|||
"mobile": "\ue7d9", |
|||
"more": "\ue7d6", |
|||
"more-fill": "\ue7d7", |
|||
"my-fill": "\ue7d4", |
|||
"my": "\ue7d5", |
|||
"principal":"\ue80d", |
|||
"notice-fill": "\ue7d2", |
|||
"notice": "\ue7d3", |
|||
"order": "\ue7d0", |
|||
"order-fill": "\ue7d1", |
|||
"picture": "\ue7ce", |
|||
"picture-fill": "\ue7cf", |
|||
"setup-fill": "\ue7cc", |
|||
"setup": "\ue7cd", |
|||
"share": "\ue7ca", |
|||
"share-fill": "\ue7cb", |
|||
"shop": "\ue7c8", |
|||
"shop-fill": "\ue7c9", |
|||
"star-fill": "\ue7c5", |
|||
"star": "\ue7c6", |
|||
"starhalf": "\ue7c7", |
|||
"stepon-fill": "\ue7c3", |
|||
"stepon": "\ue7c4", |
|||
"wait-fill": "\ue7c1", |
|||
"wait": "\ue7c2", |
|||
"warning": "\ue7bf", |
|||
"warning-fill": "\ue7c0", |
|||
"plus": "\ue7bc", |
|||
"plussign-fill": "\ue7bd", |
|||
"plussign": "\ue7be", |
|||
"minus": "\ue7b9", |
|||
"minussign": "\ue7ba", |
|||
"minussign-fill": "\ue7bb", |
|||
"close": "\ue7b8", |
|||
"clear": "\ue7b6", |
|||
"clear-fill": "\ue7b7", |
|||
"checkbox-fill": "\ue7b5", |
|||
"checkround": "\ue7b4", |
|||
"checkbox": "\ue7b3", |
|||
"check": "\ue7b2", |
|||
"pulldown-fill": "\ue7ae", |
|||
"pullup": "\ue7af", |
|||
"pullup-fill": "\ue7b0", |
|||
"pulldown": "\ue7b1", |
|||
"roundright-fill": "\ue7ac", |
|||
"roundright": "\ue7ad", |
|||
"arrowright": "\ue7a9", |
|||
"arrowleft": "\ue7aa", |
|||
"arrowdown": "\ue7ab", |
|||
"left": "\ue7a6", |
|||
"up": "\ue7a7", |
|||
"right": "\ue7a8", |
|||
"back": "\ue7a3", |
|||
"top": "\ue7a4", |
|||
"dropdown": "\ue7a5", |
|||
"turningleft": "\ue79f", |
|||
"turningup": "\ue7a0", |
|||
"turningright": "\ue7a1", |
|||
"turningdown": "\ue7a2", |
|||
"refresh": "\ue79c", |
|||
"loading": "\ue79d", |
|||
"search": "\ue79e", |
|||
"rotate": "\ue79b", |
|||
"screen": "\ue79a", |
|||
"signin": "\ue799", |
|||
"calendar": "\ue798", |
|||
"scan": "\ue797", |
|||
"qrcode": "\ue796", |
|||
"wallet": "\ue795", |
|||
"telephone": "\ue794", |
|||
"visible": "\ue793", |
|||
"invisible": "\ue792", |
|||
"menu": "\ue78e", |
|||
"operate": "\ue78f", |
|||
"slide": "\ue790", |
|||
"list": "\ue791", |
|||
"nonetwork": "\ue78d", |
|||
"partake": "\ue78c", |
|||
"qa": "\ue78b", |
|||
"barchart": "\ue788", |
|||
"piechart": "\ue789", |
|||
"linechart": "\ue78a", |
|||
"at": "\ue787", |
|||
"face": "\ue77f", |
|||
"redpacket": "\ue780", |
|||
"suspend": "\ue781", |
|||
"link": "\ue782", |
|||
"keyboard": "\ue783", |
|||
"play": "\ue784", |
|||
"video": "\ue785", |
|||
"voice": "\ue786", |
|||
"sina": "\ue77a", |
|||
"browser": "\ue77b", |
|||
"moments": "\ue77c", |
|||
"qq": "\ue77d", |
|||
"wechat": "\ue77e", |
|||
"balance": "\ue779", |
|||
"bankcardpay": "\ue778", |
|||
"wxpay": "\ue777", |
|||
"alipay": "\ue776" |
|||
} |
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -0,0 +1,217 @@ |
|||
<template> |
|||
<view class="fui-list__cell" :class="{'fui-highlight':highlight,'fui-list__cell-background':!background}" |
|||
:style="{paddingTop:padding[0] || 0,paddingRight:padding[1] || 0,paddingBottom:padding[2] || padding[0] || 0,paddingLeft:padding[3] || padding[1] || 0,background:background,marginTop:marginTop+'rpx',marginBottom:marginBottom+'rpx',borderRadius:radius}" |
|||
@tap="handleClick"> |
|||
<view v-if="topBorder" :style="{background:borderColor,left:topLeft+'rpx',right:topRight+'rpx'}" |
|||
class="fui-cell__border-top" :class="{'fui-cell__border-color':!borderColor}"></view> |
|||
<slot></slot> |
|||
<view v-if="bottomBorder" :style="{background:borderColor,left:bottomLeft+'rpx',right:bottomRight+'rpx'}" |
|||
class="fui-cell__border-bottom" :class="{'fui-cell__border-color':!borderColor}"></view> |
|||
<view class="fui-cell__arrow" v-if="arrow" :style="{'border-color':arrowColor}"> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: "fui-list-cell", |
|||
emits: ['click'], |
|||
props: { |
|||
//padding值,上、右、下、左,nvue下padding-right(右)无效 |
|||
padding: { |
|||
type: Array, |
|||
default () { |
|||
return ['32rpx', '32rpx'] |
|||
} |
|||
}, |
|||
//margin-top 单位rpx |
|||
marginTop: { |
|||
type: [Number, String], |
|||
default: 0 |
|||
}, |
|||
//margin-bottom 单位rpx |
|||
marginBottom: { |
|||
type: [Number, String], |
|||
default: 0 |
|||
}, |
|||
//背景颜色 |
|||
// #ifdef APP-NVUE |
|||
background: { |
|||
type: String, |
|||
default: '#fff' |
|||
}, |
|||
// #endif |
|||
// #ifndef APP-NVUE |
|||
background: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
// #endif |
|||
//是否有点击效果 |
|||
highlight: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
//是否需要右侧箭头 |
|||
arrow: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
arrowColor: { |
|||
type: String, |
|||
default: '#B2B2B2' |
|||
}, |
|||
//是否显示上边框 |
|||
topBorder: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
//是否显示下边框 |
|||
bottomBorder: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
//边框颜色,非nvue下传值则全局默认样式失效 |
|||
// #ifdef APP-NVUE |
|||
borderColor: { |
|||
type: String, |
|||
default: '#EEEEEE' |
|||
}, |
|||
// #endif |
|||
// #ifndef APP-NVUE |
|||
borderColor: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
// #endif |
|||
//上边框left值,单位rpx |
|||
topLeft: { |
|||
type: [Number, String], |
|||
default: 0 |
|||
}, |
|||
//上边框right值,单位rpx |
|||
topRight: { |
|||
type: [Number, String], |
|||
default: 0 |
|||
}, |
|||
//下边框left值,单位rpx |
|||
bottomLeft: { |
|||
type: [Number, String], |
|||
default: 32 |
|||
}, |
|||
//下边框right值,单位rpx |
|||
bottomRight: { |
|||
type: [Number, String], |
|||
default: 0 |
|||
}, |
|||
//border-radius圆角值 |
|||
radius: { |
|||
type: String, |
|||
default: '0' |
|||
}, |
|||
index: { |
|||
type: Number, |
|||
default: 0 |
|||
} |
|||
}, |
|||
methods: { |
|||
handleClick() { |
|||
this.$emit('click', { |
|||
index: this.index |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.fui-list__cell { |
|||
position: relative; |
|||
flex: 1; |
|||
/* #ifndef APP-NVUE */ |
|||
width: 100%; |
|||
display: flex; |
|||
box-sizing: border-box; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
/* #ifdef APP-NVUE */ |
|||
.fui-list__item { |
|||
flex: 1; |
|||
} |
|||
|
|||
/* #endif */ |
|||
.fui-cell__arrow { |
|||
height: 40rpx; |
|||
width: 40rpx; |
|||
border-width: 3px 3px 0 0; |
|||
border-style: solid; |
|||
transform: rotate(45deg) scale(0.5); |
|||
/* #ifndef APP-NVUE */ |
|||
border-radius: 4rpx; |
|||
flex-shrink: 0; |
|||
margin-left: auto; |
|||
box-sizing: border-box; |
|||
/* #endif */ |
|||
/* #ifdef APP-NVUE */ |
|||
border-top-right-radius: 3rpx; |
|||
/* #endif */ |
|||
transform-origin: center center; |
|||
margin-right: -5.8579rpx; |
|||
} |
|||
|
|||
.fui-cell__border-top { |
|||
position: absolute; |
|||
top: 0; |
|||
/* #ifdef APP-NVUE */ |
|||
height: 0.5px; |
|||
z-index: -1; |
|||
/* #endif */ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
height: 1px; |
|||
-webkit-transform: scaleY(0.5); |
|||
transform: scaleY(0.5); |
|||
transform-origin: 0 0; |
|||
z-index: 1; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.fui-cell__border-bottom { |
|||
position: absolute; |
|||
bottom: 0; |
|||
/* #ifdef APP-NVUE */ |
|||
height: 0.5px; |
|||
z-index: -1; |
|||
/* #endif */ |
|||
/* #ifndef APP-NVUE */ |
|||
height: 1px; |
|||
-webkit-transform: scaleY(0.5); |
|||
transform: scaleY(0.5); |
|||
transform-origin: 0 100%; |
|||
z-index: 1; |
|||
/* #endif */ |
|||
} |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
.fui-cell__border-color { |
|||
background-color: var(--fui-color-border, #EEEEEE) !important; |
|||
} |
|||
.fui-list__cell-background { |
|||
background-color: var(--fui-bg-color, #fff); |
|||
} |
|||
|
|||
/* #endif */ |
|||
.fui-highlight:active { |
|||
/* #ifdef APP-NVUE */ |
|||
background-color: rgba(0, 0, 0, 0.2) !important; |
|||
/* #endif */ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
background-color: var(--fui-bg-color-hover, rgba(0, 0, 0, 0.2)) !important; |
|||
/* #endif */ |
|||
} |
|||
</style> |
@ -0,0 +1,85 @@ |
|||
/* |
|||
FirstUI组件内置的基础变量 |
|||
1.如果你是组件使用者,你可以通过修改这些变量的值来定制自己的组件主题,实现自定义主题功能 |
|||
2.如果全局修改需要在项目根目录下App.vue文件中引入此css文件 |
|||
3.如果组件中有props属性是针对颜色设置(默认为空值),则优先级:props变量(如果有传值)> 全局主题色 |
|||
*/ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
page { |
|||
/* 行为相关颜色 */ |
|||
--fui-color-primary: #465CFF; |
|||
--fui-color-success: #09BE4F; |
|||
--fui-color-warning: #FFB703; |
|||
--fui-color-danger: #FF2B2B; |
|||
--fui-color-purple: #6831FF; |
|||
|
|||
/* 文字基本颜色、其他辅助色 */ |
|||
/* 用于重量级文字信息、标题 */ |
|||
--fui-color-title: #181818; |
|||
/* 用于普通级段落信息、引导词 */ |
|||
--fui-color-section: #333333; |
|||
/* 用于次要标题内容 */ |
|||
--fui-color-subtitle: #7F7F7F; |
|||
/* 用于底部标签、描述、次要文字信息 */ |
|||
--fui-color-label: #B2B2B2; |
|||
/* 用于辅助、次要信息、禁用文字等。如:待输入状态描述文字,已点击按钮文字 */ |
|||
--fui-color-minor: #CCCCCC; |
|||
--fui-color-white: #FFFFFF; |
|||
/* 链接颜色 */ |
|||
--fui-color-link: #465CFF; |
|||
|
|||
|
|||
/* 背景颜色 */ |
|||
--fui-bg-color: #ffffff; |
|||
/* 页面背景底色 */ |
|||
--fui-bg-color-grey: #F1F4FA; |
|||
/* 内容模块底色 */ |
|||
--fui-bg-color-content: #F8F8F8; |
|||
--fui-bg-color-red: rgba(255, 43, 43, .05); |
|||
--fui-bg-color-yellow: rgba(255, 183, 3, .1); |
|||
--fui-bg-color-purple: rgba(104, 49, 255, .05); |
|||
--fui-bg-color-green: rgba(9, 190, 79, .05); |
|||
/* 点击背景色 */ |
|||
--fui-bg-color-hover: rgba(0, 0, 0, 0.2); |
|||
/* 遮罩颜色 */ |
|||
--fui-bg-color-mask: rgba(0, 0, 0, 0.6); |
|||
|
|||
|
|||
/* 边框颜色 */ |
|||
--fui-color-border: #EEEEEE; |
|||
|
|||
/* 阴影颜色 */ |
|||
--fui-color-shadow: rgba(2, 4, 38, 0.05); |
|||
|
|||
/*禁用态的透明度 */ |
|||
--fui-opacity-disabled: 0.5; |
|||
|
|||
/* 图标尺寸 */ |
|||
--fui-img-size-sm: 48rpx; |
|||
--fui-img-size-base: 56rpx; |
|||
--fui-img-size-middle: 64rpx; |
|||
--fui-img-size-lg: 96rpx; |
|||
|
|||
/* 图片尺寸 */ |
|||
--fui-img-sm: 60rpx; |
|||
--fui-img-base: 120rpx; |
|||
--fui-img-lg: 240rpx; |
|||
|
|||
/* Border Radius */ |
|||
--fui-border-radius-sm: 16rpx; |
|||
--fui-border-radius-base: 24rpx; |
|||
--fui-border-radius-lg: 48rpx; |
|||
--fui-border-radius-circle: 50%; |
|||
|
|||
/* 水平间距 */ |
|||
--fui-spacing-row-sm: 16rpx; |
|||
--fui-spacing-row-base: 24rpx; |
|||
--fui-spacing-row-lg: 32rpx; |
|||
|
|||
/* 垂直间距 */ |
|||
--fui-spacing-col-sm: 8rpx; |
|||
--fui-spacing-col-base: 16rpx; |
|||
--fui-spacing-col-lg: 24rpx; |
|||
} |
|||
/* #endif */ |
@ -0,0 +1,21 @@ |
|||
<template> |
|||
<view class="grid-out"> |
|||
<slot></slot> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
methods: { |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.grid-out { |
|||
display: flex; |
|||
align-content: flex-start; |
|||
flex-flow: row wrap; |
|||
} |
|||
</style> |
@ -0,0 +1,68 @@ |
|||
<template> |
|||
<view class="async-switch"> |
|||
<switch :checked="checked" :disabled="disabled" :color="changeColor" /> |
|||
<button @click="onChange"></button> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name:'async-switch', |
|||
props:{ |
|||
checked: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
disabled:{ |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
color:{ |
|||
type:String, |
|||
default:'#007aff' |
|||
}, |
|||
disabledColor:{ |
|||
type:String, |
|||
default:'#333' |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
|
|||
}; |
|||
}, |
|||
computed:{ |
|||
changeColor(){ |
|||
return (this.disabled ? this.disabledColor : this.color) |
|||
} |
|||
}, |
|||
methods:{ |
|||
onChange(){ |
|||
/* 禁用状态不返回事件 */ |
|||
if(this.disabled){ |
|||
return; |
|||
} |
|||
this.$emit("change"); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
.async-switch{ |
|||
display: inline-block; |
|||
position: relative; |
|||
|
|||
&>switch{ |
|||
margin: 0; |
|||
} |
|||
&>button{ |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
opacity: 0; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,186 @@ |
|||
<template> |
|||
<view style="display: flex;flex-direction: column;box-sizing: border-box;"> |
|||
|
|||
<view v-if="border == 1 || border ==3" class="border"></view> |
|||
|
|||
<view style="width: 100%;height: 30rpx;"></view> |
|||
<view style="display: flex;flex-direction: row;align-items: center;margin-left: 16rpx;margin-right: 15rpx;"> |
|||
<text style="font-size: 26rpx;color: #101010;" :class="{ 'enabled': !enabled }">{{title}}</text> |
|||
<image v-if="required" src="../../static/asterisk.png" |
|||
style="width: 14rpx;height: 16rpx;margin-left: 12rpx;flex-shrink: 0;"></image> |
|||
</view> |
|||
<view style="width: 100%;height: 30rpx;"></view> |
|||
<view style="margin-left: 16rpx;margin-right: 15rpx;" :class="{ 'enabled': !enabled }"> |
|||
|
|||
<!--自定义类型的选择器--> |
|||
<!--自定义数据 需提供数据源 以及 取值key--> |
|||
<picker v-if="type==0" :disabled="!enabled" :range-key="dataDetailKey" :range="data" @change="custom"> |
|||
<view style="display: flex;flex-direction: row;align-items: center;width: 100%;"> |
|||
<view v-if="select == undefined || select == null || select === ''" |
|||
style="font-size: 28rpx;color: #BCBCBC;">{{hint}}</view> |
|||
<view v-if="select != undefined && select != null || select != ''" |
|||
style="font-size: 28rpx;color: #2E2D2D;">{{select}}</view> |
|||
<view style="flex: 1;"></view> |
|||
<image src="../../static/drop.png" |
|||
style="width: 45rpx;height: 45rpx;margin-left: 12rpx;flex-shrink: 0;"> |
|||
</image> |
|||
</view> |
|||
</picker> |
|||
|
|||
<!--内置选择器 支持 男女 以及 是否--> |
|||
<picker v-if="type==1||type==2" :disabled="!enabled" range-key="detail" :range="type==1?sex:yes" |
|||
@change="preset"> |
|||
<view style="display: flex;flex-direction: row;align-items: center;width: 100%;"> |
|||
<view v-if="select == undefined || select == null || select === ''" |
|||
style="font-size: 28rpx;color: #BCBCBC;">{{hint}}</view> |
|||
<view v-if="select != undefined && select != null || select != ''" |
|||
style="font-size: 28rpx;color: #2E2D2D;">{{select}}</view> |
|||
<view style="flex: 1;"></view> |
|||
<image src="../../static/drop.png" |
|||
style="width: 45rpx;height: 45rpx;margin-left: 12rpx;flex-shrink: 0;"> |
|||
</image> |
|||
</view> |
|||
</picker> |
|||
|
|||
<!--用户自行实现 选择的方式--> |
|||
<view v-if="type!=0&&type!=1&&type!=2" |
|||
style="display: flex;flex-direction: row;align-items: center;width: 100%;" @click="impl"> |
|||
<view v-if="select == undefined || select == null || select === ''" |
|||
style="font-size: 28rpx;color: #BCBCBC;">{{hint}}</view> |
|||
<view v-if="select != undefined && select != null || select != ''" |
|||
style="font-size: 28rpx;color: #2E2D2D;">{{select}}</view> |
|||
<view style="flex: 1;"></view> |
|||
<image src="../../static/drop.png" |
|||
style="width: 45rpx;height: 45rpx;margin-left: 12rpx;flex-shrink: 0;"> |
|||
</image> |
|||
</view> |
|||
|
|||
</view> |
|||
<view style="width: 100%;height: 25rpx;"></view> |
|||
|
|||
<view v-if="border == 2 || border ==3" class="border"></view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: "item-drop", |
|||
props: { |
|||
// 是否必填,即是否显示星号 |
|||
required: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
// 标题 |
|||
title: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
// 提示信息 |
|||
hint: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
// 显示的内容 |
|||
content: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
// 分割线 |
|||
// 0 无上线分割线 |
|||
// 1 只有上分割线 |
|||
// 2 只有下分割线 |
|||
// 3 上下都有分割线 |
|||
border: { |
|||
type: Number, |
|||
default: 2 |
|||
}, |
|||
// 是否可使用 |
|||
enabled: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
// 下拉类型 |
|||
// 0自定义数据 |
|||
// 1男女 |
|||
// 2是否 |
|||
// 3自己实现 |
|||
type: { |
|||
type: Number, |
|||
default: 0 |
|||
}, |
|||
// 弹出数据 |
|||
data: { |
|||
type: Array, |
|||
default: [] |
|||
}, |
|||
// 弹出数据选择取值key |
|||
dataDetailKey: { |
|||
type: String, |
|||
default: "" |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.select = this.content |
|||
}, |
|||
data() { |
|||
return { |
|||
sex: [{ |
|||
"detail": "男", |
|||
"value": "0001" |
|||
}, |
|||
{ |
|||
"detail": "女", |
|||
"value": "0002" |
|||
} |
|||
], |
|||
yes: [{ |
|||
"detail": "是", |
|||
"value": "0001" |
|||
}, |
|||
{ |
|||
"detail": "否", |
|||
"value": "0002" |
|||
} |
|||
], |
|||
select: "", |
|||
selector: {} |
|||
}; |
|||
}, |
|||
methods: { |
|||
custom(e) { |
|||
let index = e.detail.value |
|||
// 选中的 |
|||
this.selector = this.data[index] |
|||
// 选择的值 |
|||
this.select = this.selector[this.dataDetailKey] |
|||
console.log("选择+") |
|||
console.log(this.selector) |
|||
}, |
|||
preset(e) { |
|||
let index = e.detail.value |
|||
// 选中的 |
|||
this.selector = this.type == 1 ? this.sex[index] : this.yes[index] |
|||
// 选择的值 |
|||
console.log("选择+") |
|||
console.log(this.selector) |
|||
}, |
|||
impl() { |
|||
if (this.enabled) |
|||
// 对方提供监听 |
|||
this.$emit("select") |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.border { |
|||
height: 1rpx; |
|||
background-color: #DFDFDF; |
|||
} |
|||
|
|||
.enabled { |
|||
opacity: 0.5; |
|||
} |
|||
</style> |
@ -0,0 +1,149 @@ |
|||
<template> |
|||
<view style="display: flex;flex-direction: column;box-sizing: border-box;"> |
|||
|
|||
<view v-if="border == 1 || border == 3" class="border"></view> |
|||
|
|||
<view style="width: 100%;height: 30rpx;"></view> |
|||
<view style="display: flex;flex-direction: row;align-items: center;margin-left: 16rpx;margin-right: 15rpx;"> |
|||
<text style="font-size: 26rpx;color: #101010;" :class="{ 'enabled': !enabled }">{{title}}</text> |
|||
<image v-if="required" src="../../static/asterisk.png" |
|||
style="width: 14rpx;height: 16rpx;margin-left: 12rpx;flex-shrink: 0;"></image> |
|||
</view> |
|||
<view style="width: 100%;height: 30rpx;"></view> |
|||
|
|||
<!--选择类型ocr识别--> |
|||
<view v-if="data.length > 0" |
|||
style="display: flex;flex-direction: row;align-items: center;margin-left: 16rpx;margin-right: 15rpx;" |
|||
:class="{ 'enabled': !enabled }"> |
|||
<input style="font-size: 28rpx;color: #2E2D2D;flex: 1;" placeholder-style="font-size: 28rpx;color: #BCBCBC;" |
|||
:value="input" @input="inputValue" :placeholder="hint" /> |
|||
<picker :disabled="!enabled" :range-key="dataDetailKey" :range="data" @change="custom"> |
|||
<image src="../../static/scan.png" |
|||
style="width: 45rpx;height: 45rpx;margin-left: 12rpx;flex-shrink: 0;"> |
|||
</picker> |
|||
</image> |
|||
</view> |
|||
|
|||
<!--默认身份证类型--> |
|||
<view v-if="data.length == 0" |
|||
style="display: flex;flex-direction: row;align-items: center;margin-left: 16rpx;margin-right: 15rpx;" |
|||
:class="{ 'enabled': !enabled }"> |
|||
<input style="font-size: 28rpx;color: #2E2D2D;flex: 1;" placeholder-style="font-size: 28rpx;color: #BCBCBC;" |
|||
:value="input" @input="inputValue" :placeholder="hint" /> |
|||
<image src="../../static/scan.png" style="width: 45rpx;height: 45rpx;margin-left: 12rpx;flex-shrink: 0;" |
|||
@click="scan"> |
|||
</image> |
|||
</view> |
|||
|
|||
<view style="width: 100%;height: 25rpx;"></view> |
|||
|
|||
<view v-if="border == 2 || border ==3" class="border"></view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
//引入配置 |
|||
import B64 from '../../commons/LocalFile2Base64.js'; |
|||
|
|||
export default { |
|||
name: "item-scan", |
|||
props: { |
|||
// 是否必填,即是否显示星号 |
|||
required: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
// 标题 |
|||
title: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
// 提示信息 |
|||
hint: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
// 显示的内容 |
|||
content: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
// 分割线 |
|||
// 0 无上线分割线 |
|||
// 1 只有上分割线 |
|||
// 2 只有下分割线 |
|||
// 3 上下都有分割线 |
|||
border: { |
|||
type: Number, |
|||
default: 2 |
|||
}, |
|||
// 是否可使用 |
|||
enabled: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
// 弹出数据 |
|||
data: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
// 弹出数据选择取值key(识别类型的) |
|||
dataDetailKey: { |
|||
type: String, |
|||
default: "" |
|||
} |
|||
}, |
|||
created() { |
|||
this.input = this.content |
|||
}, |
|||
data() { |
|||
return { |
|||
input: "", |
|||
select: "" |
|||
}; |
|||
}, |
|||
methods: { |
|||
scan() { |
|||
if (this.enabled) { |
|||
let _this = this; |
|||
uni.chooseImage({ |
|||
count: 1, |
|||
sizeType: ['original'], |
|||
success: function(res) { |
|||
B64(res.tempFilePaths[0]) |
|||
.then((res) => { |
|||
// 成功 |
|||
console.log(res) |
|||
}, (err) => { |
|||
// 失败 |
|||
_this.Toast(err) |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
}, |
|||
inputValue(e) { |
|||
console.log(e.detail.value) |
|||
}, |
|||
custom(e) { |
|||
let index = e.detail.value |
|||
// ocr类型 |
|||
this.select = this.data[index][this.dataDetailKey] |
|||
this.scan() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.border { |
|||
height: 1rpx; |
|||
background-color: #DFDFDF; |
|||
} |
|||
|
|||
.enabled { |
|||
opacity: 0.5; |
|||
} |
|||
</style> |
@ -0,0 +1,41 @@ |
|||
<template> |
|||
<view> |
|||
<view style="width: 100%;margin-top: 10rpx;display: flex;justify-content: center;"> |
|||
<image src="../../static/custom-icon/jiazai.png" mode="aspectFill" class="loading spin"></image> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return {}; |
|||
}, |
|||
methods: { |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.loading { |
|||
margin-top: 10rpx; |
|||
width: 20px; |
|||
height: 20px; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.spin { |
|||
animation: spin 1s steps(8) infinite; |
|||
} |
|||
|
|||
@keyframes spin { |
|||
from { |
|||
transform: rotate(0deg); |
|||
} |
|||
|
|||
to { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,56 @@ |
|||
/* 下拉刷新区域 */ |
|||
.mescroll-downwarp { |
|||
position: absolute; |
|||
top: -100%; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
text-align: center; |
|||
} |
|||
|
|||
/* 下拉刷新--内容区,定位于区域底部 */ |
|||
.mescroll-downwarp .downwarp-content { |
|||
position: absolute; |
|||
left: 0; |
|||
bottom: 0; |
|||
width: 100%; |
|||
min-height: 60rpx; |
|||
padding: 20rpx 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
/* 下拉刷新--提示文本 */ |
|||
.mescroll-downwarp .downwarp-tip { |
|||
display: inline-block; |
|||
font-size: 28rpx; |
|||
vertical-align: middle; |
|||
margin-left: 16rpx; |
|||
/* color: gray; 已在style设置color,此处删去*/ |
|||
} |
|||
|
|||
/* 下拉刷新--旋转进度条 */ |
|||
.mescroll-downwarp .downwarp-progress { |
|||
display: inline-block; |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
border-radius: 50%; |
|||
border: 2rpx solid gray; |
|||
border-bottom-color: transparent !important; |
|||
/*已在style设置border-color,此处需加 !important*/ |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
/* 旋转动画 */ |
|||
.mescroll-downwarp .mescroll-rotate { |
|||
animation: mescrollDownRotate 0.6s linear infinite; |
|||
} |
|||
|
|||
@keyframes mescrollDownRotate { |
|||
0% { |
|||
transform: rotate(0deg); |
|||
} |
|||
|
|||
100% { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
<!-- 下拉刷新区域 --> |
|||
<template> |
|||
<view v-if="mOption.use" class="mescroll-downwarp" |
|||
:style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" |
|||
:style="{'border-color':mescroll.optDown.textColor, 'transform':downRotate}"></view> |
|||
<view class="downwarp-tip">{{downText}}</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
option: Object, // down的配置项 |
|||
type: Number, // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4) |
|||
rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1) |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption() { |
|||
return this.option || {} |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading() { |
|||
return this.type === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate() { |
|||
return 'rotate(' + 360 * this.rate + 'deg)' |
|||
}, |
|||
// 文本提示 |
|||
downText() { |
|||
switch (this.type) { |
|||
case 1: |
|||
return this.mOption.textInOffset; |
|||
case 2: |
|||
return this.mOption.textOutOffset; |
|||
case 3: |
|||
return this.mOption.textLoading; |
|||
case 4: |
|||
return this.mOption.textLoading; |
|||
default: |
|||
return this.mOption.textInOffset; |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "./mescroll-down.css"; |
|||
</style> |
@ -0,0 +1,92 @@ |
|||
<!--空布局 |
|||
|
|||
可作为独立的组件, 不使用mescroll的页面也能单独引入, 以便APP全局统一管理: |
|||
import MescrollEmpty from '@/components/mescroll-uni/components/mescroll-empty.vue'; |
|||
<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty> |
|||
|
|||
--> |
|||
<template> |
|||
<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" |
|||
:style="{ 'z-index': option.zIndex, top: option.top }"> |
|||
<image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> |
|||
<view v-if="tip" class="empty-tip">{{ tip }}</view> |
|||
<view v-if="option.btnText" class="empty-btn" @click="emptyClick">{{ option.btnText }}</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// 引入全局配置 |
|||
import GlobalOption from './../mescroll-uni-option.js'; |
|||
export default { |
|||
props: { |
|||
// empty的配置项: 默认为GlobalOption.up.empty |
|||
option: { |
|||
type: Object, |
|||
default () { |
|||
return {}; |
|||
} |
|||
} |
|||
}, |
|||
// 使用computed获取配置,用于支持option的动态配置 |
|||
computed: { |
|||
// 图标 |
|||
icon() { |
|||
return this.option.icon == null ? GlobalOption.up.empty.icon : this.option.icon; // 此处不使用短路求值, 用于支持传空串不显示图标 |
|||
}, |
|||
// 文本提示 |
|||
tip() { |
|||
return this.option.tip == null ? GlobalOption.up.empty.tip : this.option.tip; // 此处不使用短路求值, 用于支持传空串不显示文本提示 |
|||
} |
|||
}, |
|||
methods: { |
|||
// 点击按钮 |
|||
emptyClick() { |
|||
this.$emit('emptyclick'); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
/* 无任何数据的空布局 */ |
|||
.mescroll-empty { |
|||
box-sizing: border-box; |
|||
width: 100%; |
|||
padding: 100rpx 50rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.mescroll-empty.empty-fixed { |
|||
z-index: 99; |
|||
position: absolute; |
|||
/*transform会使fixed失效,最终会降级为absolute */ |
|||
top: 100rpx; |
|||
left: 0; |
|||
} |
|||
|
|||
.mescroll-empty .empty-icon { |
|||
width: 280rpx; |
|||
height: 280rpx; |
|||
} |
|||
|
|||
.mescroll-empty .empty-tip { |
|||
margin-top: 20rpx; |
|||
font-size: 24rpx; |
|||
color: gray; |
|||
} |
|||
|
|||
.mescroll-empty .empty-btn { |
|||
display: inline-block; |
|||
margin-top: 40rpx; |
|||
min-width: 200rpx; |
|||
padding: 18rpx; |
|||
font-size: 28rpx; |
|||
border: 1rpx solid #e04b28; |
|||
border-radius: 60rpx; |
|||
color: #e04b28; |
|||
} |
|||
|
|||
.mescroll-empty .empty-btn:active { |
|||
opacity: 0.75; |
|||
} |
|||
</style> |
@ -0,0 +1,80 @@ |
|||
<!-- 回到顶部的按钮 --> |
|||
<template> |
|||
<image class="mescroll-totop" |
|||
:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-safe-bottom': mOption.safearea}]" |
|||
:style="{'z-index':mOption.zIndex, 'left': left, 'right': right, 'bottom':addUnit(mOption.bottom), 'width':addUnit(mOption.width), 'border-radius':addUnit(mOption.radius)}" |
|||
src="/static/img//uniapp/mescrollTotop.png" mode="widthFix" @click="toTopClick" /> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
// up.toTop的配置项 |
|||
option: Object, |
|||
// 是否显示 |
|||
value: false |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption() { |
|||
return this.option || {} |
|||
}, |
|||
// 优先显示左边 |
|||
left() { |
|||
return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto'; |
|||
}, |
|||
// 右边距离 (优先显示左边) |
|||
right() { |
|||
return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right); |
|||
} |
|||
}, |
|||
methods: { |
|||
addUnit(num) { |
|||
if (!num) return 0; |
|||
if (typeof num === 'number') return num + 'rpx'; |
|||
return num |
|||
}, |
|||
toTopClick() { |
|||
this.$emit('input', false); // 使v-model生效 |
|||
this.$emit('click'); // 派发点击事件 |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
/* 回到顶部的按钮 */ |
|||
.mescroll-totop { |
|||
z-index: 9990; |
|||
position: fixed !important; |
|||
/* 加上important避免编译到H5,在多mescroll中定位失效 */ |
|||
right: 20rpx; |
|||
bottom: 120rpx; |
|||
width: 72rpx; |
|||
height: auto; |
|||
border-radius: 50%; |
|||
opacity: 0; |
|||
transition: opacity 0.5s; |
|||
/* 过渡 */ |
|||
margin-bottom: var(--window-bottom); |
|||
/* css变量 */ |
|||
} |
|||
|
|||
/* 适配 iPhoneX */ |
|||
.mescroll-safe-bottom { |
|||
margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); |
|||
/* window-bottom + 适配 iPhoneX */ |
|||
margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom)); |
|||
} |
|||
|
|||
/* 显示 -- 淡入 */ |
|||
.mescroll-totop-in { |
|||
opacity: 1; |
|||
} |
|||
|
|||
/* 隐藏 -- 淡出且不接收事件*/ |
|||
.mescroll-totop-out { |
|||
opacity: 0; |
|||
pointer-events: none; |
|||
} |
|||
</style> |
@ -0,0 +1,47 @@ |
|||
/* 上拉加载区域 */ |
|||
.mescroll-upwarp { |
|||
min-height: 60rpx; |
|||
padding: 30rpx 0; |
|||
text-align: center; |
|||
clear: both; |
|||
} |
|||
|
|||
/*提示文本 */ |
|||
.mescroll-upwarp .upwarp-tip, |
|||
.mescroll-upwarp .upwarp-nodata { |
|||
display: inline-block; |
|||
font-size: 28rpx; |
|||
vertical-align: middle; |
|||
/* color: gray; 已在style设置color,此处删去*/ |
|||
} |
|||
|
|||
.mescroll-upwarp .upwarp-tip { |
|||
margin-left: 16rpx; |
|||
} |
|||
|
|||
/*旋转进度条 */ |
|||
.mescroll-upwarp .upwarp-progress { |
|||
display: inline-block; |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
border-radius: 50%; |
|||
border: 2rpx solid gray; |
|||
border-bottom-color: transparent !important; |
|||
/*已在style设置border-color,此处需加 !important*/ |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
/* 旋转动画 */ |
|||
.mescroll-upwarp .mescroll-rotate { |
|||
animation: mescrollUpRotate 0.6s linear infinite; |
|||
} |
|||
|
|||
@keyframes mescrollUpRotate { |
|||
0% { |
|||
transform: rotate(0deg); |
|||
} |
|||
|
|||
100% { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
@ -0,0 +1,39 @@ |
|||
<!-- 上拉加载区域 --> |
|||
<template> |
|||
<view class="mescroll-upwarp" :style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-if="isUpLoading"> |
|||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view> |
|||
<view class="upwarp-tip">{{ mOption.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
option: Object, // up的配置项 |
|||
type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了) |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption() { |
|||
return this.option || {}; |
|||
}, |
|||
// 加载中 |
|||
isUpLoading() { |
|||
return this.type === 1; |
|||
}, |
|||
// 没有更多了 |
|||
isUpNoMore() { |
|||
return this.type === 2; |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import './mescroll-up.css'; |
|||
</style> |
@ -0,0 +1,15 @@ |
|||
page { |
|||
-webkit-overflow-scrolling: touch; |
|||
/* 使iOS滚动流畅 */ |
|||
} |
|||
|
|||
.mescroll-body { |
|||
position: relative; |
|||
/* 下拉刷新区域相对自身定位 */ |
|||
height: auto; |
|||
/* 不可固定高度,否则overflow: hidden, 可通过设置最小高度使列表不满屏仍可下拉*/ |
|||
overflow: hidden; |
|||
/* 遮住顶部下拉刷新区域 */ |
|||
box-sizing: border-box; |
|||
/* 避免设置padding出现双滚动条的问题 */ |
|||
} |
@ -0,0 +1,309 @@ |
|||
<template> |
|||
<view class="mescroll-body" |
|||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom, 'padding-bottom': padBottomConstant, 'padding-bottom': padBottomEnv }" |
|||
@touchstart="touchstartEvent" @touchmove="touchmoveEvent" @touchend="touchendEvent" |
|||
@touchcancel="touchendEvent"> |
|||
<view class="mescroll-body-content" :style="{ transform: translateY, transition: transition }"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" |
|||
:style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" |
|||
:style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view> |
|||
<view class="downwarp-tip">{{downText}}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 列表内容 --> |
|||
<slot></slot> |
|||
|
|||
<!-- 空布局 --> |
|||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty> |
|||
|
|||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)--> |
|||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view v-if="mescroll.optUp.use && !isDownLoading" class="mescroll-upwarp" |
|||
:style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-if="upLoadType===1"> |
|||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"> |
|||
</view> |
|||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// 引入mescroll-uni.js,处理核心逻辑 |
|||
import MeScroll from './mescroll-uni.js'; |
|||
// 引入全局配置 |
|||
import GlobalOption from './mescroll-uni-option.js'; |
|||
// 引入空布局组件 |
|||
import MescrollEmpty from './components/mescroll-empty.vue'; |
|||
// 引入回到顶部组件 |
|||
import MescrollTop from './components/mescroll-top.vue'; |
|||
|
|||
export default { |
|||
components: { |
|||
MescrollEmpty, |
|||
MescrollTop |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: { |
|||
optDown: {}, |
|||
optUp: {} |
|||
}, // mescroll实例 |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1) |
|||
downLoadType: 4, // 下拉刷新状态 (inOffset:1, outOffset:2, showLoading:3, endDownScroll:4) |
|||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
statusBarHeight: 0, // 状态栏高度 |
|||
isSafearea: false // 支持安全区 |
|||
}; |
|||
}, |
|||
props: { |
|||
down: Object, // 下拉刷新的参数配置 |
|||
up: Object, // 上拉加载的参数配置 |
|||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
topbar: Boolean, // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可自动加上状态栏高度的偏移量) |
|||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
height: [String, Number] // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
}, |
|||
computed: { |
|||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
minHeight() { |
|||
return this.toPx(this.height || '100%') + 'px' |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) + (this.topbar ? this.statusBarHeight : 0); |
|||
}, |
|||
padTop() { |
|||
return this.numTop + 'px'; |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom); |
|||
}, |
|||
padBottom() { |
|||
return this.numBottom + 'px'; |
|||
}, |
|||
padBottomConstant() { |
|||
return this.isSafearea ? 'calc(' + this.padBottom + ' + constant(safe-area-inset-bottom))' : this |
|||
.padBottom; |
|||
}, |
|||
padBottomEnv() { |
|||
return this.isSafearea ? 'calc(' + this.padBottom + ' + env(safe-area-inset-bottom))' : this.padBottom; |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
isDownReset() { |
|||
return this.downLoadType === 3 || this.downLoadType === 4; |
|||
}, |
|||
// 过渡 |
|||
transition() { |
|||
return this.isDownReset ? 'transform 300ms' : this.downTransition; |
|||
}, |
|||
translateY() { |
|||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : |
|||
''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading() { |
|||
return this.downLoadType === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate() { |
|||
return 'rotate(' + 360 * this.downRate + 'deg)' |
|||
}, |
|||
// 文本提示 |
|||
downText() { |
|||
switch (this.downLoadType) { |
|||
case 1: |
|||
return this.mescroll.optDown.textInOffset; |
|||
case 2: |
|||
return this.mescroll.optDown.textOutOffset; |
|||
case 3: |
|||
return this.mescroll.optDown.textLoading; |
|||
case 4: |
|||
return this.mescroll.optDown.textLoading; |
|||
default: |
|||
return this.mescroll.optDown.textInOffset; |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
//number,rpx,upx,px,% --> px的数值 |
|||
toPx(num) { |
|||
if (typeof num === 'string') { |
|||
if (num.indexOf('px') !== -1) { |
|||
if (num.indexOf('rpx') !== -1) { |
|||
// "10rpx" |
|||
num = num.replace('rpx', ''); |
|||
} else if (num.indexOf('upx') !== -1) { |
|||
// "10upx" |
|||
num = num.replace('upx', ''); |
|||
} else { |
|||
// "10px" |
|||
return Number(num.replace('px', '')); |
|||
} |
|||
} else if (num.indexOf('%') !== -1) { |
|||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|||
let rate = Number(num.replace('%', '')) / 100; |
|||
return this.windowHeight * rate; |
|||
} |
|||
} |
|||
return num ? uni.upx2px(Number(num)) : 0; |
|||
}, |
|||
//注册列表touchstart事件,用于下拉刷新 |
|||
touchstartEvent(e) { |
|||
this.mescroll.touchstartEvent(e); |
|||
}, |
|||
//注册列表touchmove事件,用于下拉刷新 |
|||
touchmoveEvent(e) { |
|||
this.mescroll.touchmoveEvent(e); |
|||
}, |
|||
//注册列表touchend事件,用于下拉刷新 |
|||
touchendEvent(e) { |
|||
this.mescroll.touchendEvent(e); |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
emptyClick() { |
|||
this.$emit('emptyclick', this.mescroll); |
|||
}, |
|||
// 点击回到顶部的按钮回调 |
|||
toTopClick() { |
|||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|||
} |
|||
}, |
|||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|||
created() { |
|||
let vm = this; |
|||
|
|||
let diyOption = { |
|||
// 下拉刷新的配置 |
|||
down: { |
|||
inOffset(mescroll) { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset(mescroll) { |
|||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
onMoving(mescroll, rate, downHight) { |
|||
// 下拉过程中的回调,滑动过程一直在执行; |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1) |
|||
}, |
|||
showLoading(mescroll, downHight) { |
|||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
endDownScroll(mescroll) { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll); |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll() { |
|||
vm.upLoadType = 0; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
MeScroll.extend(diyOption, GlobalOption); // 混入全局的配置 |
|||
let myOption = JSON.parse( |
|||
JSON.stringify({ |
|||
down: vm.down, |
|||
up: vm.up |
|||
}) |
|||
); // 深拷贝,避免对props的影响 |
|||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置 |
|||
|
|||
// 初始化MeScroll对象 |
|||
vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域 |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
// mescroll-body在Android小程序下拉会卡顿,无法像mescroll-uni那样通过设置"disableScroll":true解决,只能用动画过渡缓解 |
|||
// #ifdef MP |
|||
if (sys.platform == "android") vm.downTransition = 'transform 200ms' |
|||
// #endif |
|||
|
|||
// 因为使用的是page的scroll,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
uni.pageScrollTo({ |
|||
scrollTop: y, |
|||
duration: t |
|||
}) |
|||
}); |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (sys.platform == "ios") { |
|||
vm.isSafearea = vm.safearea; |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
} else { |
|||
vm.isSafearea = false |
|||
vm.mescroll.optUp.toTop.safearea = false |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "./mescroll-body.css"; |
|||
@import "./components/mescroll-down.css"; |
|||
@import './components/mescroll-up.css'; |
|||
</style> |
@ -0,0 +1,60 @@ |
|||
// mescroll-body 和 mescroll-uni 通用
|
|||
|
|||
// import MescrollUni from "./mescroll-uni.vue";
|
|||
// import MescrollBody from "./mescroll-body.vue";
|
|||
|
|||
const MescrollMixin = { |
|||
// components: { // 非H5端无法通过mixin注册组件, 只能在main.js中注册全局组件或具体界面中注册
|
|||
// MescrollUni,
|
|||
// MescrollBody
|
|||
// },
|
|||
data() { |
|||
return { |
|||
mescroll: null //mescroll实例对象
|
|||
} |
|||
}, |
|||
// 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
onPullDownRefresh() { |
|||
this.mescroll && this.mescroll.onPullDownRefresh(); |
|||
}, |
|||
// 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
|
|||
onPageScroll(e) { |
|||
this.mescroll && this.mescroll.onPageScroll(e); |
|||
}, |
|||
// 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
|
|||
onReachBottom() { |
|||
this.mescroll && this.mescroll.onReachBottom(); |
|||
}, |
|||
methods: { |
|||
// mescroll组件初始化的回调,可获取到mescroll对象
|
|||
mescrollInit(mescroll) { |
|||
this.mescroll = mescroll; |
|||
this.mescrollInitByRef(); // 兼容字节跳动小程序
|
|||
}, |
|||
// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序: http://www.mescroll.com/qa.html?v=20200107#q26)
|
|||
mescrollInitByRef() { |
|||
if (!this.mescroll || !this.mescroll.resetUpScroll) { |
|||
let mescrollRef = this.$refs.mescrollRef; |
|||
if (mescrollRef) this.mescroll = mescrollRef.mescroll |
|||
} |
|||
}, |
|||
// 下拉刷新的回调
|
|||
downCallback() { |
|||
// mixin默认resetUpScroll
|
|||
this.mescroll.resetUpScroll() |
|||
}, |
|||
// 上拉加载的回调
|
|||
upCallback() { |
|||
// mixin默认延时500自动结束加载
|
|||
setTimeout(() => { |
|||
this.mescroll.endErr(); |
|||
}, 500) |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
|
|||
} |
|||
|
|||
} |
|||
|
|||
export default MescrollMixin; |
@ -0,0 +1,34 @@ |
|||
// 全局配置
|
|||
// mescroll-body 和 mescroll-uni 通用
|
|||
const GlobalOption = { |
|||
down: { |
|||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
|||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
|||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
|||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
}, |
|||
up: { |
|||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textNoMore: '-- END --', // 没有更多数据的提示文本
|
|||
offset: 80, // 距底部多远时,触发upCallback
|
|||
isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
|
|||
toTop: { |
|||
// 回到顶部按钮,需配置src才显示
|
|||
src: "/static/img/uniapp/mescrollTotop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
|
|||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
|
|||
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
|||
}, |
|||
empty: { |
|||
use: true, // 是否显示空布局
|
|||
icon: "/static/img/uniapp/zanwushuju.png", // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
|||
tip: '' // 提示
|
|||
} |
|||
} |
|||
} |
|||
|
|||
export default GlobalOption |
@ -0,0 +1,33 @@ |
|||
page { |
|||
height: 100%; |
|||
box-sizing: border-box; |
|||
/* 避免设置padding出现双滚动条的问题 */ |
|||
} |
|||
|
|||
.mescroll-uni-warp { |
|||
height: 100%; |
|||
} |
|||
|
|||
.mescroll-uni { |
|||
position: relative; |
|||
width: 100%; |
|||
height: 100%; |
|||
min-height: 200rpx; |
|||
overflow-y: auto; |
|||
box-sizing: border-box; |
|||
/* 避免设置padding出现双滚动条的问题 */ |
|||
} |
|||
|
|||
/* 定位的方式固定高度 */ |
|||
.mescroll-uni-fixed { |
|||
z-index: 1; |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
width: auto; |
|||
/* 使right生效 */ |
|||
height: auto; |
|||
/* 使bottom生效 */ |
|||
} |
@ -0,0 +1,869 @@ |
|||
/* mescroll |
|||
* version 1.2.5 |
|||
* 2020-03-15 wenju |
|||
* http://www.mescroll.com
|
|||
*/ |
|||
|
|||
export default function MeScroll(options, isScrollBody) { |
|||
let me = this; |
|||
me.version = '1.2.5'; // mescroll版本号
|
|||
me.options = options || {}; // 配置
|
|||
me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view
|
|||
|
|||
me.isDownScrolling = false; // 是否在执行下拉刷新的回调
|
|||
me.isUpScrolling = false; // 是否在执行上拉加载的回调
|
|||
let hasDownCallback = me.options.down && me.options.down.callback; // 是否配置了down的callback
|
|||
|
|||
// 初始化下拉刷新
|
|||
me.initDownScroll(); |
|||
// 初始化上拉加载,则初始化
|
|||
me.initUpScroll(); |
|||
|
|||
// 自动加载
|
|||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
|||
// 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新)
|
|||
if (me.optDown.use && me.optDown.auto && hasDownCallback) { |
|||
if (me.optDown.autoShowLoading) { |
|||
me.triggerDownScroll(); // 显示下拉进度,执行下拉回调
|
|||
} else { |
|||
me.optDown.callback && me.optDown.callback(me); // 不显示下拉进度,直接执行下拉回调
|
|||
} |
|||
} |
|||
// 自动触发上拉加载
|
|||
setTimeout(function() { // 延时确保先执行down的callback,再执行up的callback,因为部分小程序emit是异步,会导致isUpAutoLoad判断有误
|
|||
me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll(); |
|||
}, 100) |
|||
}, 30); // 需让me.optDown.inited和me.optUp.inited先执行
|
|||
} |
|||
|
|||
/* 配置参数:下拉刷新 */ |
|||
MeScroll.prototype.extendDownScroll = function(optDown) { |
|||
// 下拉刷新的配置
|
|||
MeScroll.extend(optDown, { |
|||
use: true, // 是否启用下拉刷新; 默认true
|
|||
auto: true, // 是否在初始化完毕之后自动执行下拉刷新的回调; 默认true
|
|||
native: false, // 是否使用系统自带的下拉刷新; 默认false; 仅mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false
|
|||
isLock: false, // 是否锁定下拉刷新,默认false;
|
|||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
|||
startTop: 100, // scroll-view滚动到顶部时,此时的scroll-top不一定为0, 此值用于控制最大的误差
|
|||
fps: 80, // 下拉节流 (值越大每秒刷新频率越高)
|
|||
inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
|
|||
outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
|
|||
bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行
|
|||
minAngle: 45, // 向下滑动最少偏移的角度,取值区间 [0,90];默认45度,即向下滑动的角度大于45度则触发下拉;而小于45度,将不触发下拉,避免与左右滑动的轮播等组件冲突;
|
|||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
|||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorTop)
|
|||
textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
|
|||
inited: null, // 下拉刷新初始化完毕的回调
|
|||
inOffset: null, // 下拉的距离进入offset范围内那一刻的回调
|
|||
outOffset: null, // 下拉的距离大于offset那一刻的回调
|
|||
onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
|
|||
beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】
|
|||
showLoading: null, // 显示下拉刷新进度的回调
|
|||
afterLoading: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
|
|||
endDownScroll: null, // 结束下拉刷新的回调
|
|||
callback: function(mescroll) { |
|||
// 下拉刷新的回调;默认重置上拉加载列表为第一页
|
|||
mescroll.resetUpScroll(); |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/* 配置参数:上拉加载 */ |
|||
MeScroll.prototype.extendUpScroll = function(optUp) { |
|||
// 上拉加载的配置
|
|||
MeScroll.extend(optUp, { |
|||
use: true, // 是否启用上拉加载; 默认true
|
|||
auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
|
|||
isLock: false, // 是否锁定上拉加载,默认false;
|
|||
isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发;
|
|||
isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
|
|||
callback: null, // 上拉加载的回调;function(page,mescroll){ }
|
|||
page: { |
|||
num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
|
|||
size: 10, // 每页数据的数量
|
|||
time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
|
|||
}, |
|||
noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看
|
|||
offset: 80, // 距底部多远时,触发upCallback
|
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textNoMore: '-- END --', // 没有更多数据的提示文本
|
|||
bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorBottom)
|
|||
textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
|
|||
inited: null, // 初始化完毕的回调
|
|||
showLoading: null, // 显示加载中的回调
|
|||
showNoMore: null, // 显示无更多数据的回调
|
|||
hideUpScroll: null, // 隐藏上拉加载的回调
|
|||
errDistance: 60, // endErr的时候需往上滑动一段距离,使其往下滑动时再次触发onReachBottom,仅mescroll-body生效
|
|||
toTop: { |
|||
// 回到顶部按钮,需配置src才显示
|
|||
src: null, // 图片路径,默认null (绝对路径或网络图)
|
|||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000
|
|||
duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项)
|
|||
btnClick: null, // 点击按钮的回调
|
|||
onShow: null, // 是否显示的回调
|
|||
zIndex: 9990, // fixed定位z-index值
|
|||
left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|||
right: 20, // 到右边的距离, 默认20 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|||
bottom: 120, // 到底部的距离, 默认120 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|||
safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false, 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取本vue的safearea值)
|
|||
width: 72, // 回到顶部图标的宽度, 默认72 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|||
radius: "50%" // 圆角, 默认"50%" (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
|||
}, |
|||
empty: { |
|||
use: true, // 是否显示空布局
|
|||
icon: null, // 图标路径
|
|||
tip: '~ 暂无相关数据 ~', // 提示
|
|||
btnText: '', // 按钮
|
|||
btnClick: null, // 点击按钮的回调
|
|||
onShow: null, // 是否显示的回调
|
|||
fixed: false, // 是否使用fixed定位,默认false; 配置fixed为true,以下的top和zIndex才生效 (transform会使fixed失效,最终会降级为absolute)
|
|||
top: "100rpx", // fixed定位的top值 (完整的单位值,如 "10%"; "100rpx")
|
|||
zIndex: 99 // fixed定位z-index值
|
|||
}, |
|||
onScroll: false // 是否监听滚动事件
|
|||
}) |
|||
} |
|||
|
|||
/* 配置参数 */ |
|||
MeScroll.extend = function(userOption, defaultOption) { |
|||
if (!userOption) return defaultOption; |
|||
for (let key in defaultOption) { |
|||
if (userOption[key] == null) { |
|||
let def = defaultOption[key]; |
|||
if (def != null && typeof def === 'object') { |
|||
userOption[key] = MeScroll.extend({}, def); // 深度匹配
|
|||
} else { |
|||
userOption[key] = def; |
|||
} |
|||
} else if (typeof userOption[key] === 'object') { |
|||
MeScroll.extend(userOption[key], defaultOption[key]); // 深度匹配
|
|||
} |
|||
} |
|||
return userOption; |
|||
} |
|||
|
|||
/* 简单判断是否配置了颜色 (非透明,非白色) */ |
|||
MeScroll.prototype.hasColor = function(color) { |
|||
if (!color) return false; |
|||
let c = color.toLowerCase(); |
|||
return c != "#fff" && c != "#ffffff" && c != "transparent" && c != "white" |
|||
} |
|||
|
|||
/* -------初始化下拉刷新------- */ |
|||
MeScroll.prototype.initDownScroll = function() { |
|||
let me = this; |
|||
// 配置参数
|
|||
me.optDown = me.options.down || {}; |
|||
if (!me.optDown.textColor && me.hasColor(me.optDown.bgColor)) me.optDown.textColor = |
|||
"#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
|
|||
me.extendDownScroll(me.optDown); |
|||
|
|||
// 如果是mescroll-body且配置了native,则禁止自定义的下拉刷新
|
|||
if (me.isScrollBody && me.optDown.native) { |
|||
me.optDown.use = false |
|||
} else { |
|||
me.optDown.native = false // 仅mescroll-body支持,mescroll-uni不支持
|
|||
} |
|||
|
|||
me.downHight = 0; // 下拉区域的高度
|
|||
|
|||
// 在页面中加入下拉布局
|
|||
if (me.optDown.use && me.optDown.inited) { |
|||
// 初始化完毕的回调
|
|||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
|||
me.optDown.inited(me); |
|||
}, 0) |
|||
} |
|||
} |
|||
|
|||
/* 列表touchstart事件 */ |
|||
MeScroll.prototype.touchstartEvent = function(e) { |
|||
if (!this.optDown.use) return; |
|||
|
|||
this.startPoint = this.getPoint(e); // 记录起点
|
|||
this.startTop = this.getScrollTop(); // 记录此时的滚动条位置
|
|||
this.lastPoint = this.startPoint; // 重置上次move的点
|
|||
this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
|
|||
this.inTouchend = false; // 标记不是touchend
|
|||
} |
|||
|
|||
/* 列表touchmove事件 */ |
|||
MeScroll.prototype.touchmoveEvent = function(e) { |
|||
// #ifdef H5
|
|||
window.isPreventDefault = false // 标记不需要阻止window事件
|
|||
// #endif
|
|||
|
|||
if (!this.optDown.use) return; |
|||
if (!this.startPoint) return; |
|||
let me = this; |
|||
|
|||
// 节流
|
|||
let t = new Date().getTime(); |
|||
if (me.moveTime && t - me.moveTime < me.moveTimeDiff) { // 小于节流时间,则不处理
|
|||
return; |
|||
} else { |
|||
me.moveTime = t |
|||
if (!me.moveTimeDiff) me.moveTimeDiff = 1000 / me.optDown.fps |
|||
} |
|||
|
|||
let scrollTop = me.getScrollTop(); // 当前滚动条的距离
|
|||
let curPoint = me.getPoint(e); // 当前点
|
|||
|
|||
let moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
|||
|
|||
// 向下拉 && 在顶部
|
|||
// mescroll-body,直接判定在顶部即可
|
|||
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
|
|||
// scroll-view滚动到顶部时,scrollTop不一定为0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
|
|||
if (moveY > 0 && ( |
|||
(me.isScrollBody && scrollTop <= 0) || |
|||
(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me |
|||
.startTop))) |
|||
)) { |
|||
// 可下拉的条件
|
|||
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me |
|||
.isUpScrolling && |
|||
me.optUp.isBoth))) { |
|||
|
|||
// 下拉的角度是否在配置的范围内
|
|||
let angle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
|
|||
if (angle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
|
|||
|
|||
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
|
|||
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) { |
|||
me.inTouchend = true; // 标记执行touchend
|
|||
me.touchendEvent(); // 提前触发touchend
|
|||
return; |
|||
} |
|||
|
|||
// #ifdef H5
|
|||
window.isPreventDefault = true // 标记阻止window事件
|
|||
// #endif
|
|||
me.preventDefault(e); // 阻止默认事件
|
|||
|
|||
let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
|
|||
|
|||
// 下拉距离 < 指定距离
|
|||
if (me.downHight < me.optDown.offset) { |
|||
if (me.movetype !== 1) { |
|||
me.movetype = 1; // 加入标记,保证只执行一次
|
|||
me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
|
|||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
|||
} |
|||
me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
|
|||
|
|||
// 指定距离 <= 下拉距离
|
|||
} else { |
|||
if (me.movetype !== 2) { |
|||
me.movetype = 2; // 加入标记,保证只执行一次
|
|||
me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
|
|||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
|||
} |
|||
if (diff > 0) { // 向下拉
|
|||
me.downHight += Math.round(diff * me.optDown.outOffsetRate); // 越往下,高度变化越小
|
|||
} else { // 向上收
|
|||
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
|
|||
} |
|||
} |
|||
|
|||
let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
|
|||
me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
|
|||
} |
|||
} |
|||
|
|||
me.lastPoint = curPoint; // 记录本次移动的点
|
|||
} |
|||
|
|||
/* 列表touchend事件 */ |
|||
MeScroll.prototype.touchendEvent = function(e) { |
|||
if (!this.optDown.use) return; |
|||
// 如果下拉区域高度已改变,则需重置回来
|
|||
if (this.isMoveDown) { |
|||
if (this.downHight >= this.optDown.offset) { |
|||
// 符合触发刷新的条件
|
|||
this.triggerDownScroll(); |
|||
} else { |
|||
// 不符合的话 则重置
|
|||
this.downHight = 0; |
|||
this.optDown.endDownScroll && this.optDown.endDownScroll(this); |
|||
} |
|||
this.movetype = 0; |
|||
this.isMoveDown = false; |
|||
} else if (!this.isScrollBody && this.getScrollTop() === this.startTop) { // scroll-view到顶/左/右/底的滑动事件
|
|||
let isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
|||
// 上滑
|
|||
if (isScrollUp) { |
|||
// 需检查滑动的角度
|
|||
let angle = this.getAngle(this.getPoint(e), this.startPoint); // 两点之间的角度,区间 [0,90]
|
|||
if (angle > 80) { |
|||
// 检查并触发上拉
|
|||
this.triggerUpScroll(true); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* 根据点击滑动事件获取第一个手指的坐标 */ |
|||
MeScroll.prototype.getPoint = function(e) { |
|||
if (!e) { |
|||
return { |
|||
x: 0, |
|||
y: 0 |
|||
} |
|||
} |
|||
if (e.touches && e.touches[0]) { |
|||
return { |
|||
x: e.touches[0].pageX, |
|||
y: e.touches[0].pageY |
|||
} |
|||
} else if (e.changedTouches && e.changedTouches[0]) { |
|||
return { |
|||
x: e.changedTouches[0].pageX, |
|||
y: e.changedTouches[0].pageY |
|||
} |
|||
} else { |
|||
return { |
|||
x: e.clientX, |
|||
y: e.clientY |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* 计算两点之间的角度: 区间 [0,90]*/ |
|||
MeScroll.prototype.getAngle = function(p1, p2) { |
|||
let x = Math.abs(p1.x - p2.x); |
|||
let y = Math.abs(p1.y - p2.y); |
|||
let z = Math.sqrt(x * x + y * y); |
|||
let angle = 0; |
|||
if (z !== 0) { |
|||
angle = Math.asin(y / z) / Math.PI * 180; |
|||
} |
|||
return angle |
|||
} |
|||
|
|||
/* 触发下拉刷新 */ |
|||
MeScroll.prototype.triggerDownScroll = function() { |
|||
if (this.optDown.beforeLoading && this.optDown.beforeLoading(this)) { |
|||
//return true则处于完全自定义状态
|
|||
} else { |
|||
this.showDownScroll(); // 下拉刷新中...
|
|||
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
|
|||
} |
|||
} |
|||
|
|||
/* 显示下拉进度布局 */ |
|||
MeScroll.prototype.showDownScroll = function() { |
|||
this.isDownScrolling = true; // 标记下拉中
|
|||
if (this.optDown.native) { |
|||
uni.startPullDownRefresh(); // 系统自带的下拉刷新
|
|||
this.optDown.showLoading && this.optDown.showLoading(this, 0); // 仍触发showLoading,因为上拉加载用到
|
|||
} else { |
|||
this.downHight = this.optDown.offset; // 更新下拉区域高度
|
|||
this.optDown.showLoading && this.optDown.showLoading(this, this.downHight); // 下拉刷新中...
|
|||
} |
|||
} |
|||
|
|||
/* 显示系统自带的下拉刷新时需要处理的业务 */ |
|||
MeScroll.prototype.onPullDownRefresh = function() { |
|||
this.isDownScrolling = true; // 标记下拉中
|
|||
this.optDown.showLoading && this.optDown.showLoading(this, 0); // 仍触发showLoading,因为上拉加载用到
|
|||
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
|
|||
} |
|||
|
|||
/* 结束下拉刷新 */ |
|||
MeScroll.prototype.endDownScroll = function() { |
|||
if (this.optDown.native) { // 结束原生下拉刷新
|
|||
this.isDownScrolling = false; |
|||
this.optDown.endDownScroll && this.optDown.endDownScroll(this); |
|||
uni.stopPullDownRefresh(); |
|||
return |
|||
} |
|||
let me = this; |
|||
// 结束下拉刷新的方法
|
|||
let endScroll = function() { |
|||
me.downHight = 0; |
|||
me.isDownScrolling = false; |
|||
me.optDown.endDownScroll && me.optDown.endDownScroll(me); |
|||
!me.isScrollBody && me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
|
|||
} |
|||
// 结束下拉刷新时的回调
|
|||
let delay = 0; |
|||
if (me.optDown.afterLoading) delay = me.optDown.afterLoading(me); // 结束下拉刷新的延时,单位ms
|
|||
if (typeof delay === 'number' && delay > 0) { |
|||
setTimeout(endScroll, delay); |
|||
} else { |
|||
endScroll(); |
|||
} |
|||
} |
|||
|
|||
/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */ |
|||
MeScroll.prototype.lockDownScroll = function(isLock) { |
|||
if (isLock == null) isLock = true; |
|||
this.optDown.isLock = isLock; |
|||
} |
|||
|
|||
/* 锁定上拉加载:isLock=ture,null锁定;isLock=false解锁 */ |
|||
MeScroll.prototype.lockUpScroll = function(isLock) { |
|||
if (isLock == null) isLock = true; |
|||
this.optUp.isLock = isLock; |
|||
} |
|||
|
|||
/* -------初始化上拉加载------- */ |
|||
MeScroll.prototype.initUpScroll = function() { |
|||
let me = this; |
|||
// 配置参数
|
|||
me.optUp = me.options.up || { |
|||
use: false |
|||
} |
|||
if (!me.optUp.textColor && me.hasColor(me.optUp.bgColor)) me.optUp.textColor = |
|||
"#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
|
|||
me.extendUpScroll(me.optUp); |
|||
|
|||
if (!me.optUp.isBounce) me.setBounce(false); // 不允许bounce时,需禁止window的touchmove事件
|
|||
|
|||
if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
|
|||
me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
|
|||
me.startNum = me.optUp.page.num + 1; // 记录page开始的页码
|
|||
|
|||
// 初始化完毕的回调
|
|||
if (me.optUp.inited) { |
|||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
|||
me.optUp.inited(me); |
|||
}, 0) |
|||
} |
|||
} |
|||
|
|||
/*滚动到底部的事件 (仅mescroll-body生效)*/ |
|||
MeScroll.prototype.onReachBottom = function() { |
|||
if (this.isScrollBody && !this.isUpScrolling) { // 只能支持下拉刷新的时候同时可以触发上拉加载,否则滚动到底部就需要上滑一点才能触发onReachBottom
|
|||
if (!this.optUp.isLock && this.optUp.hasNext) { |
|||
this.triggerUpScroll(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/*列表滚动事件 (仅mescroll-body生效)*/ |
|||
MeScroll.prototype.onPageScroll = function(e) { |
|||
if (!this.isScrollBody) return; |
|||
|
|||
// 更新滚动条的位置 (主要用于判断下拉刷新时,滚动条是否在顶部)
|
|||
this.setScrollTop(e.scrollTop); |
|||
|
|||
// 顶部按钮的显示隐藏
|
|||
if (e.scrollTop >= this.optUp.toTop.offset) { |
|||
this.showTopBtn(); |
|||
} else { |
|||
this.hideTopBtn(); |
|||
} |
|||
} |
|||
|
|||
/*列表滚动事件*/ |
|||
MeScroll.prototype.scroll = function(e, onScroll) { |
|||
// 更新滚动条的位置
|
|||
this.setScrollTop(e.scrollTop); |
|||
// 更新滚动内容高度
|
|||
this.setScrollHeight(e.scrollHeight); |
|||
|
|||
// 向上滑还是向下滑动
|
|||
if (this.preScrollY == null) this.preScrollY = 0; |
|||
this.isScrollUp = e.scrollTop - this.preScrollY > 0; |
|||
this.preScrollY = e.scrollTop; |
|||
|
|||
// 上滑 && 检查并触发上拉
|
|||
this.isScrollUp && this.triggerUpScroll(true); |
|||
|
|||
// 顶部按钮的显示隐藏
|
|||
if (e.scrollTop >= this.optUp.toTop.offset) { |
|||
this.showTopBtn(); |
|||
} else { |
|||
this.hideTopBtn(); |
|||
} |
|||
|
|||
// 滑动监听
|
|||
this.optUp.onScroll && onScroll && onScroll() |
|||
} |
|||
|
|||
/* 触发上拉加载 */ |
|||
MeScroll.prototype.triggerUpScroll = function(isCheck) { |
|||
if (!this.isUpScrolling && this.optUp.use && this.optUp.callback) { |
|||
// 是否校验在底部; 默认不校验
|
|||
if (isCheck === true) { |
|||
let canUp = false; |
|||
// 还有下一页 && 没有锁定 && 不在下拉中
|
|||
if (this.optUp.hasNext && !this.optUp.isLock && !this.isDownScrolling) { |
|||
if (this.getScrollBottom() <= this.optUp.offset) { // 到底部
|
|||
canUp = true; // 标记可上拉
|
|||
} |
|||
} |
|||
if (canUp === false) return; |
|||
} |
|||
this.showUpScroll(); // 上拉加载中...
|
|||
this.optUp.page.num++; // 预先加一页,如果失败则减回
|
|||
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
|
|||
this.num = this.optUp.page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
|
|||
this.size = this.optUp.page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|||
this.time = this.optUp.page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|||
this.optUp.callback(this); // 执行回调,联网加载数据
|
|||
} |
|||
} |
|||
|
|||
/* 显示上拉加载中 */ |
|||
MeScroll.prototype.showUpScroll = function() { |
|||
this.isUpScrolling = true; // 标记上拉加载中
|
|||
this.optUp.showLoading && this.optUp.showLoading(this); // 回调
|
|||
} |
|||
|
|||
/* 显示上拉无更多数据 */ |
|||
MeScroll.prototype.showNoMore = function() { |
|||
this.optUp.hasNext = false; // 标记无更多数据
|
|||
this.optUp.showNoMore && this.optUp.showNoMore(this); // 回调
|
|||
} |
|||
|
|||
/* 隐藏上拉区域**/ |
|||
MeScroll.prototype.hideUpScroll = function() { |
|||
this.optUp.hideUpScroll && this.optUp.hideUpScroll(this); // 回调
|
|||
} |
|||
|
|||
/* 结束上拉加载 */ |
|||
MeScroll.prototype.endUpScroll = function(isShowNoMore) { |
|||
if (isShowNoMore != null) { // isShowNoMore=null,不处理下拉状态,下拉刷新的时候调用
|
|||
if (isShowNoMore) { |
|||
this.showNoMore(); // isShowNoMore=true,显示无更多数据
|
|||
} else { |
|||
this.hideUpScroll(); // isShowNoMore=false,隐藏上拉加载
|
|||
} |
|||
} |
|||
this.isUpScrolling = false; // 标记结束上拉加载
|
|||
} |
|||
|
|||
/* 重置上拉加载列表为第一页 |
|||
*isShowLoading 是否显示进度布局; |
|||
* 1.默认null,不传参,则显示上拉加载的进度布局 |
|||
* 2.传参true, 则显示下拉刷新的进度布局 |
|||
* 3.传参false,则不显示上拉和下拉的进度 (常用于静默更新列表数据) |
|||
*/ |
|||
MeScroll.prototype.resetUpScroll = function(isShowLoading) { |
|||
if (this.optUp && this.optUp.use) { |
|||
let page = this.optUp.page; |
|||
this.prePageNum = page.num; // 缓存重置前的页码,加载失败可退回
|
|||
this.prePageTime = page.time; // 缓存重置前的时间,加载失败可退回
|
|||
page.num = this.startNum; // 重置为第一页
|
|||
page.time = null; // 重置时间为空
|
|||
if (!this.isDownScrolling && isShowLoading !== false) { // 如果不是下拉刷新触发的resetUpScroll并且不配置列表静默更新,则显示进度;
|
|||
if (isShowLoading == null) { |
|||
this.removeEmpty(); // 移除空布局
|
|||
this.showUpScroll(); // 不传参,默认显示上拉加载的进度布局
|
|||
} else { |
|||
this.showDownScroll(); // 传true,显示下拉刷新的进度布局,不清空列表
|
|||
} |
|||
} |
|||
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
|
|||
this.num = page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
|
|||
this.size = page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|||
this.time = page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
|
|||
this.optUp.callback && this.optUp.callback(this); // 执行上拉回调
|
|||
} |
|||
} |
|||
|
|||
/* 设置page.num的值 */ |
|||
MeScroll.prototype.setPageNum = function(num) { |
|||
this.optUp.page.num = num - 1; |
|||
} |
|||
|
|||
/* 设置page.size的值 */ |
|||
MeScroll.prototype.setPageSize = function(size) { |
|||
this.optUp.page.size = size; |
|||
} |
|||
|
|||
/* 联网回调成功,结束下拉刷新和上拉加载 |
|||
* dataSize: 当前页的数据量(必传) |
|||
* totalPage: 总页数(必传) |
|||
* systime: 服务器时间 (可空) |
|||
*/ |
|||
MeScroll.prototype.endByPage = function(dataSize, totalPage, systime) { |
|||
let hasNext; |
|||
if (this.optUp.use && totalPage != null) hasNext = this.optUp.page.num < totalPage; // 是否还有下一页
|
|||
this.endSuccess(dataSize, hasNext, systime); |
|||
} |
|||
|
|||
/* 联网回调成功,结束下拉刷新和上拉加载 |
|||
* dataSize: 当前页的数据量(必传) |
|||
* totalSize: 列表所有数据总数量(必传) |
|||
* systime: 服务器时间 (可空) |
|||
*/ |
|||
MeScroll.prototype.endBySize = function(dataSize, totalSize, systime) { |
|||
let hasNext; |
|||
if (this.optUp.use && totalSize != null) { |
|||
let loadSize = (this.optUp.page.num - 1) * this.optUp.page.size + dataSize; // 已加载的数据总数
|
|||
hasNext = loadSize < totalSize; // 是否还有下一页
|
|||
} |
|||
this.endSuccess(dataSize, hasNext, systime); |
|||
} |
|||
|
|||
/* 联网回调成功,结束下拉刷新和上拉加载 |
|||
* dataSize: 当前页的数据个数(不是所有页的数据总和),用于上拉加载判断是否还有下一页.如果不传,则会判断还有下一页 |
|||
* hasNext: 是否还有下一页,布尔类型;用来解决这个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据dataSize判断,则需翻到第三页才会知道无更多数据,如果传了hasNext,则翻到第二页即可显示无更多数据. |
|||
* systime: 服务器时间(可空);用来解决这个小问题:当准备翻下一页时,数据库新增了几条记录,此时翻下一页,前面的几条数据会和上一页的重复;这里传入了systime,那么upCallback的page.time就会有值,把page.time传给服务器,让后台过滤新加入的那几条记录 |
|||
*/ |
|||
MeScroll.prototype.endSuccess = function(dataSize, hasNext, systime) { |
|||
let me = this; |
|||
// 结束下拉刷新
|
|||
if (me.isDownScrolling) me.endDownScroll(); |
|||
|
|||
// 结束上拉加载
|
|||
if (me.optUp.use) { |
|||
let isShowNoMore; // 是否已无更多数据
|
|||
if (dataSize != null) { |
|||
let pageNum = me.optUp.page.num; // 当前页码
|
|||
let pageSize = me.optUp.page.size; // 每页长度
|
|||
// 如果是第一页
|
|||
if (pageNum === 1) { |
|||
if (systime) me.optUp.page.time = systime; // 设置加载列表数据第一页的时间
|
|||
} |
|||
if (dataSize < pageSize || hasNext === false) { |
|||
// 返回的数据不满一页时,则说明已无更多数据
|
|||
me.optUp.hasNext = false; |
|||
if (dataSize === 0 && pageNum === 1) { |
|||
// 如果第一页无任何数据且配置了空布局
|
|||
isShowNoMore = false; |
|||
me.showEmpty(); |
|||
} else { |
|||
// 总列表数少于配置的数量,则不显示无更多数据
|
|||
let allDataSize = (pageNum - 1) * pageSize + dataSize; |
|||
if (allDataSize < me.optUp.noMoreSize) { |
|||
isShowNoMore = false; |
|||
} else { |
|||
isShowNoMore = true; |
|||
} |
|||
me.removeEmpty(); // 移除空布局
|
|||
} |
|||
} else { |
|||
// 还有下一页
|
|||
isShowNoMore = false; |
|||
me.optUp.hasNext = true; |
|||
me.removeEmpty(); // 移除空布局
|
|||
} |
|||
} |
|||
|
|||
// 隐藏上拉
|
|||
me.endUpScroll(isShowNoMore); |
|||
} |
|||
} |
|||
|
|||
/* 回调失败,结束下拉刷新和上拉加载 */ |
|||
MeScroll.prototype.endErr = function(errDistance) { |
|||
// 结束下拉,回调失败重置回原来的页码和时间
|
|||
if (this.isDownScrolling) { |
|||
let page = this.optUp.page; |
|||
if (page && this.prePageNum) { |
|||
page.num = this.prePageNum; |
|||
page.time = this.prePageTime; |
|||
} |
|||
this.endDownScroll(); |
|||
} |
|||
// 结束上拉,回调失败重置回原来的页码
|
|||
if (this.isUpScrolling) { |
|||
this.optUp.page.num--; |
|||
this.endUpScroll(false); |
|||
// 如果是mescroll-body,则需往回滚一定距离
|
|||
if (this.isScrollBody && errDistance !== 0) { // 不处理0
|
|||
if (!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认
|
|||
this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离
|
|||
} |
|||
} |
|||
} |
|||
|
|||
/* 显示空布局 */ |
|||
MeScroll.prototype.showEmpty = function() { |
|||
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true) |
|||
} |
|||
|
|||
/* 移除空布局 */ |
|||
MeScroll.prototype.removeEmpty = function() { |
|||
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false) |
|||
} |
|||
|
|||
/* 显示回到顶部的按钮 */ |
|||
MeScroll.prototype.showTopBtn = function() { |
|||
if (!this.topBtnShow) { |
|||
this.topBtnShow = true; |
|||
this.optUp.toTop.onShow && this.optUp.toTop.onShow(true); |
|||
} |
|||
} |
|||
|
|||
/* 隐藏回到顶部的按钮 */ |
|||
MeScroll.prototype.hideTopBtn = function() { |
|||
if (this.topBtnShow) { |
|||
this.topBtnShow = false; |
|||
this.optUp.toTop.onShow && this.optUp.toTop.onShow(false); |
|||
} |
|||
} |
|||
|
|||
/* 获取滚动条的位置 */ |
|||
MeScroll.prototype.getScrollTop = function() { |
|||
return this.scrollTop || 0 |
|||
} |
|||
|
|||
/* 记录滚动条的位置 */ |
|||
MeScroll.prototype.setScrollTop = function(y) { |
|||
this.scrollTop = y; |
|||
} |
|||
|
|||
/* 滚动到指定位置 */ |
|||
MeScroll.prototype.scrollTo = function(y, t) { |
|||
this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法
|
|||
} |
|||
|
|||
/* 自定义scrollTo */ |
|||
MeScroll.prototype.resetScrollTo = function(myScrollTo) { |
|||
this.myScrollTo = myScrollTo |
|||
} |
|||
|
|||
/* 滚动条到底部的距离 */ |
|||
MeScroll.prototype.getScrollBottom = function() { |
|||
return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop() |
|||
} |
|||
|
|||
/* 计步器 |
|||
star: 开始值 |
|||
end: 结束值 |
|||
callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器; |
|||
t: 计步时长,传0则直接回调end值;不传则默认300ms |
|||
rate: 周期;不传则默认30ms计步一次 |
|||
* */ |
|||
MeScroll.prototype.getStep = function(star, end, callback, t, rate) { |
|||
let diff = end - star; // 差值
|
|||
if (t === 0 || diff === 0) { |
|||
callback && callback(end); |
|||
return; |
|||
} |
|||
t = t || 300; // 时长 300ms
|
|||
rate = rate || 30; // 周期 30ms
|
|||
let count = t / rate; // 次数
|
|||
let step = diff / count; // 步长
|
|||
let i = 0; // 计数
|
|||
let timer = setInterval(function() { |
|||
if (i < count - 1) { |
|||
star += step; |
|||
callback && callback(star, timer); |
|||
i++; |
|||
} else { |
|||
callback && callback(end, timer); // 最后一次直接设置end,避免计算误差
|
|||
clearInterval(timer); |
|||
} |
|||
}, rate); |
|||
} |
|||
|
|||
/* 滚动容器的高度 */ |
|||
MeScroll.prototype.getClientHeight = function(isReal) { |
|||
let h = this.clientHeight || 0 |
|||
if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
|
|||
h = this.getBodyHeight() |
|||
} |
|||
return h |
|||
} |
|||
MeScroll.prototype.setClientHeight = function(h) { |
|||
this.clientHeight = h; |
|||
} |
|||
|
|||
/* 滚动内容的高度 */ |
|||
MeScroll.prototype.getScrollHeight = function() { |
|||
return this.scrollHeight || 0; |
|||
} |
|||
MeScroll.prototype.setScrollHeight = function(h) { |
|||
this.scrollHeight = h; |
|||
} |
|||
|
|||
/* body的高度 */ |
|||
MeScroll.prototype.getBodyHeight = function() { |
|||
return this.bodyHeight || 0; |
|||
} |
|||
MeScroll.prototype.setBodyHeight = function(h) { |
|||
this.bodyHeight = h; |
|||
} |
|||
|
|||
/* 阻止浏览器默认滚动事件 */ |
|||
MeScroll.prototype.preventDefault = function(e) { |
|||
// 小程序不支持e.preventDefault
|
|||
// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止
|
|||
// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
|
|||
if (e && e.cancelable && !e.defaultPrevented) e.preventDefault() |
|||
} |
|||
|
|||
/* 是否允许下拉回弹(橡皮筋效果); true或null为允许; false禁止bounce */ |
|||
MeScroll.prototype.setBounce = function(isBounce) { |
|||
// #ifdef H5
|
|||
if (isBounce === false) { |
|||
this.optUp.isBounce = false; // 禁止
|
|||
// 标记当前页使用了mescroll (需延时,确保page已切换)
|
|||
setTimeout(function() { |
|||
let uniPageDom = document.getElementsByTagName('uni-page')[0]; |
|||
uniPageDom && uniPageDom.setAttribute('use_mescroll', true) |
|||
}, 30); |
|||
// 避免重复添加事件
|
|||
if (window.isSetBounce) return; |
|||
window.isSetBounce = true; |
|||
// 需禁止window的touchmove事件才能有效的阻止bounce
|
|||
window.bounceTouchmove = function(e) { |
|||
if (!window.isPreventDefault) return; // 根据标记判断是否阻止
|
|||
|
|||
let el = e.target; |
|||
// 当前touch的元素及父元素是否要拦截touchmove事件
|
|||
let isPrevent = true; |
|||
while (el !== document.body && el !== document) { |
|||
if (el.tagName === 'UNI-PAGE') { // 只扫描当前页
|
|||
if (!el.getAttribute('use_mescroll')) { |
|||
isPrevent = false; // 如果当前页没有使用mescroll,则不阻止
|
|||
} |
|||
break; |
|||
} |
|||
let cls = el.classList; |
|||
if (cls) { |
|||
if (cls.contains('mescroll-touch')) { // 采用scroll-view 此处不能过滤mescroll-uni,否则下拉仍然有回弹
|
|||
isPrevent = false; // mescroll-touch无需拦截touchmove事件
|
|||
break; |
|||
} else if (cls.contains('mescroll-touch-x') || cls.contains('mescroll-touch-y')) { |
|||
// 如果配置了水平或者垂直滑动
|
|||
let curX = e.touches ? e.touches[0].pageX : e.clientX; // 当前第一个手指距离列表顶部的距离x
|
|||
let curY = e.touches ? e.touches[0].pageY : e.clientY; // 当前第一个手指距离列表顶部的距离y
|
|||
if (!this.preWinX) this.preWinX = curX; // 设置上次移动的距离x
|
|||
if (!this.preWinY) this.preWinY = curY; // 设置上次移动的距离y
|
|||
// 计算两点之间的角度
|
|||
let x = Math.abs(this.preWinX - curX); |
|||
let y = Math.abs(this.preWinY - curY); |
|||
let z = Math.sqrt(x * x + y * y); |
|||
this.preWinX = curX; // 记录本次curX的值
|
|||
this.preWinY = curY; // 记录本次curY的值
|
|||
if (z !== 0) { |
|||
let angle = Math.asin(y / z) / Math.PI * 180; // 角度区间 [0,90]
|
|||
if ((angle <= 45 && cls.contains('mescroll-touch-x')) || (angle > 45 && cls |
|||
.contains('mescroll-touch-y'))) { |
|||
isPrevent = false; // 水平滑动或者垂直滑动,不拦截touchmove事件
|
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
el = el.parentNode; // 继续检查其父元素
|
|||
} |
|||
// 拦截touchmove事件:是否可以被禁用&&是否已经被禁用 (这里不使用me.preventDefault(e)的方法,因为某些情况下会报找不到方法的异常)
|
|||
if (isPrevent && e.cancelable && !e.defaultPrevented && typeof e.preventDefault === "function") e |
|||
.preventDefault(); |
|||
} |
|||
window.addEventListener('touchmove', window.bounceTouchmove, { |
|||
passive: false |
|||
}); |
|||
} else { |
|||
this.optUp.isBounce = true; // 允许
|
|||
if (window.bounceTouchmove) { |
|||
window.removeEventListener('touchmove', window.bounceTouchmove); |
|||
window.bounceTouchmove = null; |
|||
window.isSetBounce = false; |
|||
} |
|||
} |
|||
// #endif
|
|||
} |
@ -0,0 +1,386 @@ |
|||
<template> |
|||
<view class="mescroll-uni-warp"> |
|||
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" |
|||
:style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'padding-bottom':padBottomConstant,'padding-bottom':padBottomEnv,'top':fixedTop,'bottom':fixedBottom,'bottom':fixedBottomConstant,'bottom':fixedBottomEnv}" |
|||
:scroll-top="scrollTop" :scroll-into-view="scrollToViewId" :scroll-with-animation="scrollAnim" |
|||
@scroll="scroll" @touchstart="touchstartEvent" @touchmove="touchmoveEvent" @touchend="touchendEvent" |
|||
@touchcancel="touchendEvent" :scroll-y='isDownReset' :enable-back-to-top="true"> |
|||
<view class="mescroll-uni-content" :style="{'transform': translateY, 'transition': transition}"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" |
|||
:style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" |
|||
:style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view> |
|||
<view class="downwarp-tip">{{downText}}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 列表内容 --> |
|||
<slot></slot> |
|||
|
|||
<!-- 空布局 --> |
|||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"> |
|||
</mescroll-empty> |
|||
|
|||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)--> |
|||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view v-if="mescroll.optUp.use && !isDownLoading" class="mescroll-upwarp" |
|||
:style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-if="upLoadType===1"> |
|||
<view class="upwarp-progress mescroll-rotate" |
|||
:style="{'border-color':mescroll.optUp.textColor}"></view> |
|||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> |
|||
</view> |
|||
</view> |
|||
</scroll-view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// 引入mescroll-uni.js,处理核心逻辑 |
|||
import MeScroll from './mescroll-uni.js'; |
|||
// 引入全局配置 |
|||
import GlobalOption from './mescroll-uni-option.js'; |
|||
// 引入空布局组件 |
|||
import MescrollEmpty from './components/mescroll-empty.vue'; |
|||
// 引入回到顶部组件 |
|||
import MescrollTop from './components/mescroll-top.vue'; |
|||
|
|||
export default { |
|||
components: { |
|||
MescrollEmpty, |
|||
MescrollTop |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: { |
|||
optDown: {}, |
|||
optUp: {} |
|||
}, // mescroll实例 |
|||
viewId: 'id_' + Math.random().toString(36).substr(2), // 随机生成mescroll的id(不能数字开头,否则找不到元素) |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1) |
|||
downLoadType: 4, // 下拉刷新状态 (inOffset:1, outOffset:2, showLoading:3, endDownScroll:4) |
|||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
scrollTop: 0, // 滚动条的位置 |
|||
scrollAnim: false, // 是否开启滚动动画 |
|||
windowTop: 0, // 可使用窗口的顶部位置 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
statusBarHeight: 0, // 状态栏高度 |
|||
isSafearea: false, // 支持安全区 |
|||
scrollToViewId: '' // 滚动到指定view的id |
|||
} |
|||
}, |
|||
props: { |
|||
down: Object, // 下拉刷新的参数配置 |
|||
up: Object, // 上拉加载的参数配置 |
|||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
topbar: Boolean, // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可自动加上状态栏高度的偏移量) |
|||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
fixed: { // 是否通过fixed固定mescroll的高度, 默认true |
|||
type: Boolean, |
|||
default () { |
|||
return true |
|||
} |
|||
}, |
|||
height: [String, |
|||
Number |
|||
] // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
}, |
|||
computed: { |
|||
// 是否使用fixed定位 (当height有值,则不使用) |
|||
isFixed() { |
|||
return !this.height && this.fixed |
|||
}, |
|||
// mescroll的高度 |
|||
scrollHeight() { |
|||
if (this.isFixed) { |
|||
return "auto" |
|||
} else if (this.height) { |
|||
return this.toPx(this.height) + 'px' |
|||
} else { |
|||
return "100%" |
|||
} |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) + (this.topbar ? this.statusBarHeight : 0) |
|||
}, |
|||
fixedTop() { |
|||
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0 |
|||
}, |
|||
padTop() { |
|||
return !this.isFixed ? this.numTop + 'px' : 0 |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom) |
|||
}, |
|||
fixedBottom() { |
|||
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0 |
|||
}, |
|||
fixedBottomConstant() { |
|||
return this.isSafearea ? "calc(" + this.fixedBottom + " + constant(safe-area-inset-bottom))" : this |
|||
.fixedBottom |
|||
}, |
|||
fixedBottomEnv() { |
|||
return this.isSafearea ? "calc(" + this.fixedBottom + " + env(safe-area-inset-bottom))" : this.fixedBottom |
|||
}, |
|||
padBottom() { |
|||
return !this.isFixed ? this.numBottom + 'px' : 0 |
|||
}, |
|||
padBottomConstant() { |
|||
return this.isSafearea ? "calc(" + this.padBottom + " + constant(safe-area-inset-bottom))" : this.padBottom |
|||
}, |
|||
padBottomEnv() { |
|||
return this.isSafearea ? "calc(" + this.padBottom + " + env(safe-area-inset-bottom))" : this.padBottom |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
isDownReset() { |
|||
return this.downLoadType === 3 || this.downLoadType === 4 |
|||
}, |
|||
// 过渡 |
|||
transition() { |
|||
return this.isDownReset ? 'transform 300ms' : ''; |
|||
}, |
|||
translateY() { |
|||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : |
|||
''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading() { |
|||
return this.downLoadType === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate() { |
|||
return 'rotate(' + 360 * this.downRate + 'deg)' |
|||
}, |
|||
// 文本提示 |
|||
downText() { |
|||
switch (this.downLoadType) { |
|||
case 1: |
|||
return this.mescroll.optDown.textInOffset; |
|||
case 2: |
|||
return this.mescroll.optDown.textOutOffset; |
|||
case 3: |
|||
return this.mescroll.optDown.textLoading; |
|||
case 4: |
|||
return this.mescroll.optDown.textLoading; |
|||
default: |
|||
return this.mescroll.optDown.textInOffset; |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
//number,rpx,upx,px,% --> px的数值 |
|||
toPx(num) { |
|||
if (typeof num === "string") { |
|||
if (num.indexOf('px') !== -1) { |
|||
if (num.indexOf('rpx') !== -1) { // "10rpx" |
|||
num = num.replace('rpx', ''); |
|||
} else if (num.indexOf('upx') !== -1) { // "10upx" |
|||
num = num.replace('upx', ''); |
|||
} else { // "10px" |
|||
return Number(num.replace('px', '')) |
|||
} |
|||
} else if (num.indexOf('%') !== -1) { |
|||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|||
let rate = Number(num.replace("%", "")) / 100 |
|||
return this.windowHeight * rate |
|||
} |
|||
} |
|||
return num ? uni.upx2px(Number(num)) : 0 |
|||
}, |
|||
//注册列表滚动事件,用于下拉刷新和上拉加载 |
|||
scroll(e) { |
|||
this.mescroll.scroll(e.detail, () => { |
|||
this.$emit('scroll', this |
|||
.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动 |
|||
}) |
|||
}, |
|||
//注册列表touchstart事件,用于下拉刷新 |
|||
touchstartEvent(e) { |
|||
this.mescroll.touchstartEvent(e); |
|||
}, |
|||
//注册列表touchmove事件,用于下拉刷新 |
|||
touchmoveEvent(e) { |
|||
this.mescroll.touchmoveEvent(e); |
|||
}, |
|||
//注册列表touchend事件,用于下拉刷新 |
|||
touchendEvent(e) { |
|||
this.mescroll.touchendEvent(e); |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
emptyClick() { |
|||
this.$emit('emptyclick', this.mescroll) |
|||
}, |
|||
// 点击回到顶部的按钮回调 |
|||
toTopClick() { |
|||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|||
}, |
|||
// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页) |
|||
setClientHeight() { |
|||
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) { |
|||
this.isExec = true; // 避免多次获取 |
|||
this.$nextTick(() => { // 确保dom已渲染 |
|||
let view = uni.createSelectorQuery().in(this).select('#' + this.viewId); |
|||
view.boundingClientRect(data => { |
|||
this.isExec = false; |
|||
if (data) { |
|||
this.mescroll.setClientHeight(data.height); |
|||
} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次 |
|||
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1; |
|||
setTimeout(() => { |
|||
this.setClientHeight() |
|||
}, this.clientNum * 100) |
|||
} |
|||
}).exec(); |
|||
}) |
|||
} |
|||
} |
|||
}, |
|||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|||
created() { |
|||
let vm = this; |
|||
|
|||
let diyOption = { |
|||
// 下拉刷新的配置 |
|||
down: { |
|||
inOffset(mescroll) { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset(mescroll) { |
|||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
onMoving(mescroll, rate, downHight) { |
|||
// 下拉过程中的回调,滑动过程一直在执行; |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1) |
|||
}, |
|||
showLoading(mescroll, downHight) { |
|||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
endDownScroll(mescroll) { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll) |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll() { |
|||
vm.upLoadType = 0; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { // 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { // 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
// 更新容器的高度 (多mescroll的情况) |
|||
vm.setClientHeight() |
|||
} |
|||
} |
|||
} |
|||
|
|||
MeScroll.extend(diyOption, GlobalOption); // 混入全局的配置 |
|||
let myOption = JSON.parse(JSON.stringify({ |
|||
'down': vm.down, |
|||
'up': vm.up |
|||
})) // 深拷贝,避免对props的影响 |
|||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置 |
|||
|
|||
// 初始化MeScroll对象 |
|||
vm.mescroll = new MeScroll(myOption); |
|||
vm.mescroll.viewId = vm.viewId; // 附带id |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if (sys.windowTop) vm.windowTop = sys.windowTop; |
|||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
|
|||
// 因为使用的是scrollview,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡 |
|||
if (typeof y === 'string') { // 第一个参数如果为字符串,则使用scroll-into-view |
|||
vm.scrollToViewId = y; |
|||
return; |
|||
} |
|||
let curY = vm.mescroll.getScrollTop() |
|||
if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡 |
|||
vm.scrollTop = curY; |
|||
vm.$nextTick(function() { |
|||
vm.scrollTop = y |
|||
}) |
|||
} else { |
|||
vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t |
|||
vm.scrollTop = step |
|||
}, t) |
|||
} |
|||
}) |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (sys.platform == "ios") { |
|||
vm.isSafearea = vm.safearea; |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
} else { |
|||
vm.isSafearea = false |
|||
vm.mescroll.optUp.toTop.safearea = false |
|||
} |
|||
}, |
|||
mounted() { |
|||
// 设置容器的高度 |
|||
this.setClientHeight() |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
@import "./mescroll-uni.css"; |
|||
@import "./components/mescroll-down.css"; |
|||
@import './components/mescroll-up.css'; |
|||
</style> |
@ -0,0 +1,23 @@ |
|||
/** |
|||
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期: |
|||
* 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例) |
|||
* 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例) |
|||
*/ |
|||
const MescrollCompMixin = { |
|||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
|
|||
onPageScroll(e) { |
|||
let item = this.$refs["mescrollItem"]; |
|||
if (item && item.mescroll) item.mescroll.onPageScroll(e); |
|||
}, |
|||
onReachBottom() { |
|||
let item = this.$refs["mescrollItem"]; |
|||
if (item && item.mescroll) item.mescroll.onReachBottom(); |
|||
}, |
|||
// 当down的native: true时, 还需传递此方法进到子组件
|
|||
onPullDownRefresh() { |
|||
let item = this.$refs["mescrollItem"]; |
|||
if (item && item.mescroll) item.mescroll.onPullDownRefresh(); |
|||
} |
|||
} |
|||
|
|||
export default MescrollCompMixin; |
@ -0,0 +1,48 @@ |
|||
/** |
|||
* mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例) |
|||
*/ |
|||
const MescrollMoreItemMixin = { |
|||
props: { |
|||
i: Number, // 每个tab页的专属下标
|
|||
index: { // 当前tab的下标
|
|||
type: Number, |
|||
default () { |
|||
return 0 |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
downOption: { |
|||
auto: false // 不自动加载
|
|||
}, |
|||
upOption: { |
|||
auto: false // 不自动加载
|
|||
}, |
|||
isInit: false // 当前tab是否已初始化
|
|||
} |
|||
}, |
|||
watch: { |
|||
// 监听下标的变化
|
|||
index(val) { |
|||
if (this.i === val && !this.isInit) { |
|||
this.isInit = true; // 标记为true
|
|||
this.mescroll && this.mescroll.triggerDownScroll(); |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
// mescroll组件初始化的回调,可获取到mescroll对象
|
|||
mescrollInit(mescroll) { |
|||
this.mescroll = mescroll; |
|||
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序 (mescroll-mixins.js)
|
|||
// 自动加载当前tab的数据
|
|||
if (this.i === this.index) { |
|||
this.isInit = true; // 标记为true
|
|||
this.mescroll.triggerDownScroll(); |
|||
} |
|||
}, |
|||
} |
|||
} |
|||
|
|||
export default MescrollMoreItemMixin; |
@ -0,0 +1,56 @@ |
|||
/** |
|||
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期: |
|||
* 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例) |
|||
* 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例) |
|||
*/ |
|||
const MescrollMoreMixin = { |
|||
data() { |
|||
return { |
|||
tabIndex: 0 // 当前tab下标
|
|||
} |
|||
}, |
|||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
|
|||
onPageScroll(e) { |
|||
let mescroll = this.getMescroll(this.tabIndex); |
|||
mescroll && mescroll.onPageScroll(e); |
|||
}, |
|||
onReachBottom() { |
|||
let mescroll = this.getMescroll(this.tabIndex); |
|||
mescroll && mescroll.onReachBottom(); |
|||
}, |
|||
// 当down的native: true时, 还需传递此方法进到子组件
|
|||
onPullDownRefresh() { |
|||
let mescroll = this.getMescroll(this.tabIndex); |
|||
mescroll && mescroll.onPullDownRefresh(); |
|||
}, |
|||
methods: { |
|||
// 根据下标获取对应子组件的mescroll
|
|||
getMescroll(i) { |
|||
if (!this.mescrollItems) this.mescrollItems = []; |
|||
if (!this.mescrollItems[i]) { |
|||
// v-for中的refs
|
|||
let vForItem = this.$refs["mescrollItem"]; |
|||
if (vForItem) { |
|||
this.mescrollItems[i] = vForItem[i] |
|||
} else { |
|||
// 普通的refs,不可重复
|
|||
this.mescrollItems[i] = this.$refs["mescrollItem" + i]; |
|||
} |
|||
} |
|||
let item = this.mescrollItems[i] |
|||
return item ? item.mescroll : null |
|||
}, |
|||
// 切换tab,恢复滚动条位置
|
|||
tabChange(i) { |
|||
let mescroll = this.getMescroll(i); |
|||
if (mescroll) { |
|||
// 延时(比$nextTick靠谱一些),确保元素已渲染
|
|||
setTimeout(() => { |
|||
mescroll.scrollTo(mescroll.getScrollTop(), 0) |
|||
}, 30) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default MescrollMoreMixin; |
@ -0,0 +1,20 @@ |
|||
<template> |
|||
<view style="display: flex;justify-content: center;width: 100%;"> |
|||
<image style="width: 280rpx;height: 280rpx; margin-top: 130rpx;margin-bottom: 110rpx;" |
|||
src="../../static/custom-icon/zanwushuju.png" mode="aspectFill"></image> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
|
|||
}; |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
@ -0,0 +1,135 @@ |
|||
<template> |
|||
<picker mode="multiSelector" :value="multiIndex" :range="multiArray" @change="handleValueChange" |
|||
@columnchange="handleColumnChange"> |
|||
<slot></slot> |
|||
</picker> |
|||
</template> |
|||
|
|||
<script> |
|||
const CHINA_REGIONS = require('./regions.json') |
|||
export default { |
|||
props: { |
|||
defaultRegions: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
defaultRegionCode: { |
|||
type: String |
|||
}, |
|||
defaultRegion: [String, Array] |
|||
}, |
|||
data() { |
|||
return { |
|||
cityArr: CHINA_REGIONS[0].childs, |
|||
districtArr: CHINA_REGIONS[0].childs[0].childs, |
|||
multiIndex: [0, 0, 0], |
|||
isInitMultiArray: true, |
|||
} |
|||
}, |
|||
watch: { |
|||
defaultRegion: { |
|||
handler(region, oldRegion) { |
|||
if (Array.isArray(region)) { |
|||
// 避免传的是字面量的时候重复触发 |
|||
oldRegion = oldRegion || [] |
|||
if (region.join('') !== oldRegion.join('')) { |
|||
this.handleDefaultRegion(region) |
|||
} |
|||
} else if (region && region.length == 6) { |
|||
this.handleDefaultRegion(region) |
|||
} else { |
|||
console.warn('defaultRegion非有效格式') |
|||
} |
|||
}, |
|||
immediate: true, |
|||
} |
|||
}, |
|||
computed: { |
|||
multiArray() { |
|||
return this.pickedArr.map(arr => arr.map(item => item.name)) |
|||
}, |
|||
pickedArr() { |
|||
// 进行初始化 |
|||
if (this.isInitMultiArray) { |
|||
return [ |
|||
CHINA_REGIONS, |
|||
CHINA_REGIONS[0].childs, |
|||
CHINA_REGIONS[0].childs[0].childs |
|||
] |
|||
} |
|||
return [CHINA_REGIONS, this.cityArr, this.districtArr]; |
|||
} |
|||
}, |
|||
methods: { |
|||
handleColumnChange(e) { |
|||
// console.log(e); |
|||
this.isInitMultiArray = false; |
|||
const that = this; |
|||
let col = e.detail.column; |
|||
let row = e.detail.value; |
|||
that.multiIndex[col] = row; |
|||
try { |
|||
switch (col) { |
|||
case 0: |
|||
if (CHINA_REGIONS[that.multiIndex[0]].childs.length == 0) { |
|||
that.cityArr = that.districtArr = [CHINA_REGIONS[that.multiIndex[0]]] |
|||
break; |
|||
} |
|||
that.cityArr = CHINA_REGIONS[that.multiIndex[0]].childs |
|||
that.districtArr = CHINA_REGIONS[that.multiIndex[0]].childs[that.multiIndex[1]].childs |
|||
break; |
|||
case 1: |
|||
that.districtArr = CHINA_REGIONS[that.multiIndex[0]].childs[that.multiIndex[1]].childs |
|||
break; |
|||
case 2: |
|||
break; |
|||
} |
|||
} catch (e) { |
|||
// console.log(e); |
|||
that.districtArr = CHINA_REGIONS[that.multiIndex[0]].childs[0].childs |
|||
} |
|||
|
|||
}, |
|||
handleValueChange(e) { |
|||
// 结构赋值 |
|||
let [index0, index1, index2] = e.detail.value; |
|||
let [arr0, arr1, arr2] = this.pickedArr; |
|||
let address = [arr0[index0], arr1[index1], arr2[index2]]; |
|||
// console.log(address); |
|||
this.$emit('getRegion', address) |
|||
}, |
|||
handleDefaultRegion(region) { |
|||
const isCode = !Array.isArray(region) |
|||
this.isInitMultiArray = false; |
|||
let children = CHINA_REGIONS |
|||
for (let i = 0; i < 3; i++) { |
|||
for (let j = 0; j < children.length; j++) { |
|||
let condition = isCode ? children[j].code == region.slice(0, (i + 1) * 2) : children[j].name |
|||
.includes(region[i]); |
|||
if (condition) { |
|||
// 匹配成功进行赋值 |
|||
// console.log(i,j,children.length-1); |
|||
children = children[j].childs; |
|||
if (i == 0) { |
|||
this.cityArr = children |
|||
} else if (i == 1) { |
|||
this.districtArr = children |
|||
} |
|||
this.$set(this.multiIndex, i, j) |
|||
// console.log(this.multiIndex); |
|||
break; |
|||
} else { |
|||
// 首次匹配失败就用默认的初始化 |
|||
// console.log(i,j,children.length-1); |
|||
if (i == 0 && j == (children.length - 1)) { |
|||
this.isInitMultiArray = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
} |
|||
</script> |
File diff suppressed because it is too large
@ -0,0 +1,459 @@ |
|||
<template> |
|||
|
|||
<view> |
|||
<view style="padding: 0px 0px;"> |
|||
<view class="filter-content" v-for="(item, index) in menuList" :key="index" v-if="menuIndex == index"> |
|||
|
|||
<view class="filter-content-list"> |
|||
<view v-for="(detailItem,idx) in selectDetailList" :key="idx" |
|||
:class="detailItem.isSelected?'filter-content-list-item-active':'filter-content-list-item-default'" |
|||
:style="{'color': detailItem.isSelected?themeColor:'#666666'}" |
|||
@tap="sortTap(idx,selectDetailList,item.key)"> |
|||
<text>{{detailItem.title}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
selectArr: [], |
|||
result: {}, |
|||
menuIndex: 0, |
|||
selectDetailList: [], |
|||
independenceObj: {}, |
|||
selectedKey: '', |
|||
cacheSelectedObj: {}, |
|||
defaultSelectedTitleObj: {} |
|||
}; |
|||
}, |
|||
props: { |
|||
themeColor: { |
|||
type: String, |
|||
default () { |
|||
return '#D1372C' |
|||
} |
|||
}, |
|||
menuList: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
independence: { |
|||
type: Boolean, |
|||
default: true |
|||
} |
|||
}, |
|||
computed: { |
|||
selectedTitleObj() { |
|||
let obj = {} |
|||
for (let i = 0; i < this.menuList.length; i++) { |
|||
let item = this.menuList[i]; |
|||
obj[item.key] = item.title; |
|||
} |
|||
return obj; |
|||
}, |
|||
defaultSelectedObj() { // 保存初始状态 |
|||
return this.getSelectedObj() |
|||
}, |
|||
selectedObj: { |
|||
get() { |
|||
return this.getSelectedObj() |
|||
}, |
|||
set(newObj) { |
|||
return newObj; |
|||
} |
|||
|
|||
} |
|||
}, |
|||
methods: { |
|||
getSelectedObj() { |
|||
let obj = {} |
|||
for (let i = 0; i < this.menuList.length; i++) { |
|||
let item = this.menuList[i]; |
|||
if (!this.independence && item.defaultSelectedIndex != null && item.defaultSelectedIndex.toString() |
|||
.length > 0) { // 处理并列菜单默认值 |
|||
|
|||
if (item.isMutiple) { |
|||
obj[item.key] = []; |
|||
item.detailList[0].isSelected = false; |
|||
if (!Array.isArray(item.defaultSelectedIndex)) { // 如果默认值不是数组 |
|||
item.defaultSelectedIndex = [item.defaultSelectedIndex]; |
|||
} |
|||
for (let j = 0; j < item.defaultSelectedIndex.length; j++) { // 将默认选中的值放入selectedObj |
|||
item.detailList[item.defaultSelectedIndex[j]].isSelected = true; |
|||
obj[item.key].push(item.detailList[item.defaultSelectedIndex[j]].value) |
|||
} |
|||
|
|||
} else { |
|||
obj[item.key] = item.detailList[item.defaultSelectedIndex].value; |
|||
this.selectedTitleObj[item.key] = item.detailList[item.defaultSelectedIndex].title; |
|||
this.defaultSelectedTitleObj[item.key] = item.detailList[item.defaultSelectedIndex].title; |
|||
item.detailList[0].isSelected = false; |
|||
item.detailList[item.defaultSelectedIndex].isSelected = true; |
|||
} |
|||
} else { |
|||
if (item.isMutiple) { |
|||
obj[item.key] = []; |
|||
} else { |
|||
obj[item.key] = ''; |
|||
} |
|||
} |
|||
} |
|||
this.result = obj; |
|||
return obj; |
|||
}, |
|||
// 重置所有选项,包括默认选项,并更新result |
|||
resetAllSelect(callback) { |
|||
let titles = []; |
|||
for (let i = 0; i < this.menuList.length; i++) { |
|||
this.resetSelected(this.menuList[i].detailList, this.menuList[i].key); |
|||
titles[this.menuList[i].key] = this.menuList[i].title; |
|||
} |
|||
let obj = { |
|||
'result': this.result, |
|||
'titles': titles, |
|||
'isReset': true |
|||
} |
|||
this.$emit("confirm", obj); |
|||
callback(this.result); |
|||
}, |
|||
// 重置选项为设置的默认值,并更新result |
|||
resetSelectToDefault(callback) { |
|||
for (let i = 0; i < this.menuList.length; i++) { |
|||
this.selectDetailList = this.menuList[i].detailList; |
|||
|
|||
if (this.menuList[i].defaultSelectedIndex) { |
|||
if (Array.isArray(this.menuList[i].defaultSelectedIndex)) { // 把所有默认的为false的点为true |
|||
for (let j = 0; j < this.menuList[i].defaultSelectedIndex.length; j++) { |
|||
if (this.selectDetailList[this.menuList[i].defaultSelectedIndex[j]].isSelected == false) { |
|||
this.itemTap(this.menuList[i].defaultSelectedIndex[j], this.selectDetailList, this |
|||
.menuList[i].isMutiple, this |
|||
.menuList[i].key) |
|||
} |
|||
} |
|||
} else { |
|||
this.itemTap(this.menuList[i].defaultSelectedIndex, this.selectDetailList, this.menuList[i] |
|||
.isMutiple, this.menuList[ |
|||
i].key) |
|||
} |
|||
|
|||
// 获取非默认项的下标 |
|||
let unDefaultSelectedIndexArr = this.getUnDefaultSelectedIndex(this.menuList[i]) |
|||
// 把所有不是默认的为true的点为false |
|||
for (let j = 0; j < unDefaultSelectedIndexArr.length; j++) { |
|||
if (this.selectDetailList[unDefaultSelectedIndexArr[j]].isSelected == true) { |
|||
this.itemTap(unDefaultSelectedIndexArr[j], this.selectDetailList, this.menuList[i] |
|||
.isMutiple, this |
|||
.menuList[i].key) |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
this.selectedObj = this.defaultSelectedObj; |
|||
this.result = this.defaultSelectedObj; |
|||
let obj = { |
|||
'result': this.result, |
|||
'titles': this.defaultSelectedTitleObj, |
|||
'isReset': true |
|||
} |
|||
this.$emit("confirm", obj); |
|||
callback(this.result) |
|||
}, |
|||
getUnDefaultSelectedIndex(menuListItem) { // 获取非默认项 |
|||
let tempDefault = menuListItem.defaultSelectedIndex; |
|||
if (!Array.isArray(tempDefault)) { |
|||
tempDefault = [tempDefault]; |
|||
} |
|||
// 获取所有项的下标 组成新的数组 |
|||
let all = []; |
|||
for (let i = 0; i < menuListItem.detailList.length; i++) { |
|||
all.push(i) |
|||
} |
|||
// 将默认选中的数组与所有项的数组的不同值合并为一个新数组 |
|||
var unDefaultSelectedIndex = tempDefault.filter(function(v) { |
|||
return !(all.indexOf(v) > -1) |
|||
}).concat(all.filter(function(v) { |
|||
return !(tempDefault.indexOf(v) > -1) |
|||
})); |
|||
return unDefaultSelectedIndex; |
|||
}, |
|||
resetMenuList(val) { |
|||
this.menuList = val; |
|||
this.$emit('update:menuList', val) |
|||
}, |
|||
menuTabClick(index) { |
|||
this.menuIndex = index; |
|||
this.selectDetailList = this.menuList[index].detailList; |
|||
this.selectedKey = this.menuList[index].key; |
|||
// 如果是独立菜单 |
|||
if (this.independence && !this.menuList[index].isSort) { |
|||
if (JSON.stringify(this.independenceObj) == '{}') { |
|||
this.initIndependenceObj(index); |
|||
} else { |
|||
for (let key in this.independenceObj) { |
|||
if (key != this.selectedKey) { |
|||
this.initIndependenceObj(index); |
|||
this.resetSelected(this.menuList[index].detailList, this.selectedKey); |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
if (this.independence && this.menuList[index].isSort) { |
|||
|
|||
this.independenceObj = {}; |
|||
|
|||
|
|||
} |
|||
if (this.independence) { |
|||
let idx = this.menuList[index].defaultSelectedIndex; |
|||
if (idx != null && idx.toString().length > 0) { // 处理独立菜单默认值 |
|||
if (this.menuList[index].isMutiple) { |
|||
for (let i = 0; i < idx.length; i++) { |
|||
if (this.menuList[index].detailList[idx[i]].isSelected == false) { |
|||
this.itemTap(idx[i], this.menuList[index].detailList, true, this.selectedKey); |
|||
} |
|||
|
|||
} |
|||
} else { |
|||
if (this.menuList[index].detailList[idx].isSelected == false) { |
|||
|
|||
this.itemTap(idx, this.menuList[index].detailList, false, this.selectedKey); |
|||
|
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
this.selectedObj = this.selectedObj; |
|||
this.$forceUpdate(); |
|||
}, |
|||
initIndependenceObj(index) { |
|||
this.independenceObj = {}; |
|||
if (this.menuList[index].isMutiple) { |
|||
this.independenceObj[this.selectedKey] = []; |
|||
} else { |
|||
this.independenceObj[this.selectedKey] = ''; |
|||
} |
|||
}, |
|||
itemTap(index, list, isMutiple, key) { |
|||
if (isMutiple == true) { |
|||
list[index].isSelected = !list[index].isSelected; |
|||
if (index == 0) { |
|||
this.resetSelected(list, key) |
|||
if (!this.independence) { |
|||
this.selectedTitleObj[key] = list[index].title; |
|||
} |
|||
} else { |
|||
list[0].isSelected = false |
|||
if (list[index].isSelected) { |
|||
if (this.independence) { |
|||
this.independenceObj[this.selectedKey].push(list[index].value); |
|||
} else { |
|||
this.selectedObj[key].push(list[index].value); |
|||
} |
|||
} else { |
|||
list[index].isSelected = false; |
|||
if (this.independence) { |
|||
var idx = this.independenceObj[this.selectedKey].indexOf(list[index].value); |
|||
this.independenceObj[this.selectedKey].splice(idx, 1); |
|||
} else { |
|||
var idx = this.selectedObj[key].indexOf(list[index].value); |
|||
this.selectedObj[key].splice(idx, 1); |
|||
} |
|||
|
|||
} |
|||
if (this.independence) { |
|||
this.result = this.independenceObj; |
|||
} else { |
|||
this.result = this.selectedObj; |
|||
} |
|||
|
|||
} |
|||
} else { |
|||
if (index == 0) { |
|||
this.resetSelected(list, key) |
|||
if (!this.independence) { |
|||
this.selectedTitleObj[key] = list[index].title; |
|||
} |
|||
} else { |
|||
list[0].isSelected = false |
|||
if (this.independence) { |
|||
this.independenceObj[this.selectedKey] = list[index].value; |
|||
this.result = this.independenceObj; |
|||
} else { |
|||
this.selectedObj[key] = list[index].value; |
|||
this.result = this.selectedObj; |
|||
this.selectedTitleObj[key] = list[index].title; |
|||
} |
|||
|
|||
for (let i = 0; i < list.length; i++) { |
|||
if (index == i) { |
|||
list[i].isSelected = true |
|||
} else { |
|||
list[i].isSelected = false |
|||
} |
|||
} |
|||
} |
|||
} |
|||
// #ifdef H5 |
|||
this.$forceUpdate(); |
|||
// #endif |
|||
}, |
|||
resetSelected(list, key) { |
|||
if (typeof this.result[key] == 'object') { |
|||
this.result[key] = []; |
|||
this.selectedTitleObj[key] = list[0].title; |
|||
} else { |
|||
this.result[key] = ''; |
|||
this.selectedTitleObj[key] = list[0].title; |
|||
} |
|||
for (let i = 0; i < list.length; i++) { |
|||
if (i == 0) { |
|||
list[i].isSelected = true; |
|||
} else { |
|||
list[i].isSelected = false; |
|||
} |
|||
} |
|||
// #ifdef H5 |
|||
this.$forceUpdate(); |
|||
// #endif |
|||
}, |
|||
sortTap(index, list, key) { |
|||
if (this.independence) { |
|||
this.independenceObj[this.selectedKey] = list[index].value; |
|||
this.result = this.independenceObj; |
|||
} else { |
|||
this.selectedObj[key] = list[index].value; |
|||
this.result = this.selectedObj; |
|||
this.selectedTitleObj[key] = list[index].title; |
|||
} |
|||
|
|||
for (let i = 0; i < list.length; i++) { |
|||
if (index == i) { |
|||
list[i].isSelected = true; |
|||
} else { |
|||
list[i].isSelected = false; |
|||
} |
|||
} |
|||
let obj = { |
|||
'result': this.result, |
|||
'titles': this.selectedTitleObj, |
|||
'isReset': false |
|||
} |
|||
this.$emit("confirm", obj); |
|||
}, |
|||
sureClick() { |
|||
let obj = { |
|||
'result': this.result, |
|||
'titles': this.selectedTitleObj, |
|||
'isReset': false |
|||
} |
|||
this.$emit("confirm", obj); |
|||
}, |
|||
resetClick(list, key) { |
|||
this.resetSelected(list, key) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.filter-content { |
|||
background-color: #F6F7F8; |
|||
} |
|||
|
|||
.filter-content-title { |
|||
border-bottom: #EEEEEE 1px solid; |
|||
padding: 10px 15px; |
|||
font-size: 13px; |
|||
color: #999999; |
|||
} |
|||
|
|||
.filter-content-detail { |
|||
padding: 5px 15px; |
|||
} |
|||
|
|||
.filter-content-detail-item-active { |
|||
background-color: #D1372C; |
|||
color: #FFFFFF; |
|||
padding: 5px 15px; |
|||
border-radius: 20px; |
|||
margin-right: 10px; |
|||
margin-top: 10px; |
|||
display: inline-block; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.filter-content-detail-item-default { |
|||
background-color: #FFFFFF; |
|||
color: #666666; |
|||
padding: 5px 15px; |
|||
border-radius: 20px; |
|||
margin-right: 10px; |
|||
margin-top: 10px; |
|||
display: inline-block; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.filter-content-footer { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
width: 100%; |
|||
height: 45px; |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.filter-content-footer-item { |
|||
width: 50%; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
font-size: 16px; |
|||
} |
|||
|
|||
.filter-content-list { |
|||
|
|||
padding: 5px 15px; |
|||
} |
|||
|
|||
.filter-content-list-item-default { |
|||
color: #666666; |
|||
width: 100%; |
|||
padding: 10px 0px; |
|||
} |
|||
|
|||
.filter-content-list-item-default text { |
|||
width: 90%; |
|||
font-size: 14px; |
|||
display: inline-block; |
|||
} |
|||
|
|||
.filter-content-list-item-active { |
|||
color: #D1372C; |
|||
width: 100%; |
|||
padding: 10px 0px; |
|||
} |
|||
|
|||
.filter-content-list-item-active text { |
|||
font-size: 14px; |
|||
width: 90%; |
|||
display: inline-block; |
|||
} |
|||
|
|||
.filter-content-list-item-active:after { |
|||
content: '✓'; |
|||
} |
|||
</style> |
@ -0,0 +1,20 @@ |
|||
@font-face { |
|||
font-family: 'sl-font'; |
|||
src: url('data:font/truetype;charset=utf-8;base64,AAEAAAALAIAAAwAwR1NVQrD+s+0AAAE4AAAAQk9TLzI8kEgOAAABfAAAAFZjbWFwZO3RAgAAAeAAAAGGZ2x5Zh0ZI/EAAANwAAAAyGhlYWQVZkUXAAAA4AAAADZoaGVhB94DhAAAALwAAAAkaG10eAwAAAAAAAHUAAAADGxvY2EAMgBkAAADaAAAAAhtYXhwAREAKAAAARgAAAAgbmFtZT5U/n0AAAQ4AAACbXBvc3TohGjqAAAGqAAAADMAAQAAA4D/gABcBAAAAAAABAAAAQAAAAAAAAAAAAAAAAAAAAMAAQAAAAEAANxW6kVfDzz1AAsEAAAAAADZJADbAAAAANkkANsAAAAABAACZAAAAAgAAgAAAAAAAAABAAAAAwAcAAQAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKAB4ALAABREZMVAAIAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAAAAQQAAZAABQAIAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5hrmHAOA/4AAXAOAAIAAAAABAAAAAAAABAAAAAQAAAAEAAAAAAAABQAAAAMAAAAsAAAABAAAAV4AAQAAAAAAWAADAAEAAAAsAAMACgAAAV4ABAAsAAAABgAEAAEAAuYa5hz//wAA5hrmHP//AAAAAAABAAYABgAAAAEAAgAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAKAAAAAAAAAACAADmGgAA5hoAAAABAADmHAAA5hwAAAACAAAAAAAAADIAZAAEAAAAAAOlAmQAEwAWABkAGgAAEwEWMjcBNjIWFAcBBiInASY0NjIBMDEVMDEnmQFgAgoDAV8LHRUK/n8LHAv+fwoVHQFoAQJZ/qEDAwFfCxYcC/6ACwsBgAsdFf6bAgQAAAAABAAAAAADpAJkABMAFgAZABsAACUBJiIHAQYiJjQ3ATYyFwEWFAYiATAxNTAxFzEDZ/6hAwoD/qELHRUKAYELHAsBgQoVHf6YAacBXwMD/qELFhwLAYEKCv5/CxwWAWUCBAAAAAAAEgDeAAEAAAAAAAAAFQAAAAEAAAAAAAEACAAVAAEAAAAAAAIABwAdAAEAAAAAAAMACAAkAAEAAAAAAAQACAAsAAEAAAAAAAUACwA0AAEAAAAAAAYACAA/AAEAAAAAAAoAKwBHAAEAAAAAAAsAEwByAAMAAQQJAAAAKgCFAAMAAQQJAAEAEACvAAMAAQQJAAIADgC/AAMAAQQJAAMAEADNAAMAAQQJAAQAEADdAAMAAQQJAAUAFgDtAAMAAQQJAAYAEAEDAAMAAQQJAAoAVgETAAMAAQQJAAsAJgFpCkNyZWF0ZWQgYnkgaWNvbmZvbnQKaWNvbmZvbnRSZWd1bGFyaWNvbmZvbnRpY29uZm9udFZlcnNpb24gMS4waWNvbmZvbnRHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuaHR0cDovL2ZvbnRlbGxvLmNvbQAKAEMAcgBlAGEAdABlAGQAIABiAHkAIABpAGMAbwBuAGYAbwBuAHQACgBpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGMAbwBuAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwECAQMBBAAEZG93bgJ1cAAAAA==') format('truetype'); |
|||
} |
|||
|
|||
.sl-font { |
|||
font-family: "sl-font" !important; |
|||
font-size: 16px; |
|||
font-style: normal; |
|||
-webkit-font-smoothing: antialiased; |
|||
-moz-osx-font-smoothing: grayscale; |
|||
} |
|||
|
|||
.sl-down:before { |
|||
content: "\e61a"; |
|||
} |
|||
|
|||
.sl-up:before { |
|||
content: "\e61c"; |
|||
} |
@ -0,0 +1,122 @@ |
|||
<template> |
|||
<scroll-view scroll-y v-show="ifshow" @tap="ableClose" @touchmove.stop.prevent class="popup-layer"> |
|||
<view ref="popRef" class="popup-content" @tap.stop="stopEvent" :style="_location"> |
|||
<slot></slot> |
|||
</view> |
|||
</scroll-view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'popup-layer', |
|||
props: { |
|||
direction: { |
|||
type: String, |
|||
default: 'top', // 方向 top,bottom,left,right |
|||
}, |
|||
autoClose: { |
|||
type: Boolean, |
|||
default: true, |
|||
}, |
|||
isTransNav: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
navHeight: { |
|||
type: Number, |
|||
default: 0 |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
ifshow: false, // 是否展示, |
|||
translateValue: -100, // 位移距离 |
|||
timer: null, |
|||
iftoggle: false, |
|||
}; |
|||
}, |
|||
computed: { |
|||
_translate() { |
|||
if (this.isTransNav) { |
|||
const transformObj = { |
|||
'top': `transform:translateY(${-this.translateValue}%)`, |
|||
'bottom': `transform:translateY(calc(${this.translateValue}% + ${this.navHeight}px))`, |
|||
'left': `transform:translateX(${-this.translateValue}%)`, |
|||
'right': `transform:translateX(${this.translateValue}%)` |
|||
}; |
|||
return transformObj[this.direction] |
|||
} else { |
|||
const transformObj = { |
|||
'top': `transform:translateY(${-this.translateValue}%)`, |
|||
'bottom': `transform:translateY(${this.translateValue}%)`, |
|||
'left': `transform:translateX(${-this.translateValue}%)`, |
|||
'right': `transform:translateX(${this.translateValue}%)` |
|||
}; |
|||
return transformObj[this.direction] |
|||
} |
|||
|
|||
}, |
|||
_location() { |
|||
const positionValue = { |
|||
'top': 'bottom:0px;width:100%;', |
|||
'bottom': 'top:0px;width:100%;', |
|||
'left': 'right:0px;height:100%;', |
|||
'right': 'left:0px;height:100%;', |
|||
}; |
|||
return positionValue[this.direction] + this._translate; |
|||
} |
|||
}, |
|||
methods: { |
|||
show() { |
|||
let _this = this; |
|||
this.ifshow = true; |
|||
let _open = setTimeout(() => { |
|||
this.translateValue = 0; |
|||
_open = null; |
|||
}, 100) |
|||
let _toggle = setTimeout(() => { |
|||
this.iftoggle = true; |
|||
_toggle = null; |
|||
}, 300); |
|||
}, |
|||
close() { |
|||
if (this.timer !== null || !this.iftoggle) { |
|||
return; |
|||
} |
|||
this.translateValue = -100 - this.navHeight; |
|||
|
|||
this.timer = setTimeout(() => { |
|||
this.ifshow = false; |
|||
this.timer = null; |
|||
this.iftoggle = false; |
|||
}, 300); |
|||
this.$emit("close") |
|||
}, |
|||
ableClose() { |
|||
if (this.autoClose) { |
|||
this.close(); |
|||
} |
|||
}, |
|||
stopEvent(event) {}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.popup-layer { |
|||
position: absolute; |
|||
z-index: 999999; |
|||
background: rgba(0, 0, 0, .3); |
|||
height: calc(100% - 50px); |
|||
width: 100%; |
|||
left: 0px; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.popup-content { |
|||
position: absolute; |
|||
z-index: 1000000; |
|||
background: #FFFFFF; |
|||
transition: all .3s ease; |
|||
} |
|||
</style> |
@ -0,0 +1,301 @@ |
|||
<template> |
|||
<view class="content"> |
|||
<view :style="{height: tabHeight + 1 +'px'}"> |
|||
<view :class="topFixed?'select-tab-fixed-top':'select-tab'" :style="{height: tabHeight+'px'}"> |
|||
<view class="select-tab-item" :style="{width: itemWidth}" v-for="(item,index) in titleList" :key="index" |
|||
@tap="showMenuClick(index)"> |
|||
<text style="display:-webkit-box;-webkit-line-clamp:1; |
|||
overflow:hidden;text-overflow:ellipsis;-webkit-box-orient:vertical; |
|||
word-break:break-all; " :style="{color:color}">{{item.title}}</text> |
|||
<text class="arrows sl-font" :class="statusList[index].isActive?up:down"></text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<popup-layer ref="popupRef" :direction="'bottom'" @close="close" :isTransNav="isTransNav" :navHeight="navHeight" |
|||
:tabHeight="tabHeight"> |
|||
<sl-filter-view :ref="'slFilterView'" :independence="independence" :themeColor="themeColor" |
|||
:menuList.sync="menuListTemp" ref="slFilterView" @confirm="filterResult"></sl-filter-view> |
|||
</popup-layer> |
|||
</view> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
import popupLayer from '@/components/sl-filter/popup-layer.vue'; |
|||
import slFilterView from '@/components/sl-filter/filter-view.vue'; |
|||
export default { |
|||
components: { |
|||
popupLayer, |
|||
slFilterView |
|||
}, |
|||
props: { |
|||
menuList: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
themeColor: { |
|||
type: String, |
|||
default () { |
|||
return '#000000' |
|||
} |
|||
}, |
|||
color: { |
|||
type: String, |
|||
default () { |
|||
return '#666666' |
|||
} |
|||
}, |
|||
independence: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
isTransNav: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
navHeight: { |
|||
type: Number, |
|||
default: 0 |
|||
}, |
|||
topFixed: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
|
|||
computed: { |
|||
itemWidth() { |
|||
return 'calc(100%/2)' |
|||
}, |
|||
menuListTemp: { |
|||
get() { |
|||
return this.getMenuListTemp(); |
|||
}, |
|||
set(newObj) { |
|||
return newObj; |
|||
} |
|||
} |
|||
}, |
|||
created: function() { |
|||
let arr = []; |
|||
let titleArr = []; |
|||
let r = {}; |
|||
for (let i = 0; i < this.menuList.length; i++) { |
|||
arr.push({ |
|||
'isActive': false |
|||
}); |
|||
r[this.menuList[i].key] = this.menuList[i].title; |
|||
|
|||
if (this.menuList[i].reflexTitle && this.menuList[i].defaultSelectedIndex > -1) { |
|||
titleArr.push({ |
|||
'title': this.menuList[i].detailList[this.menuList[i].defaultSelectedIndex].title, |
|||
'key': this.menuList[i].key |
|||
}) |
|||
} else { |
|||
titleArr.push({ |
|||
'title': this.menuList[i].title, |
|||
'key': this.menuList[i].key |
|||
}) |
|||
} |
|||
|
|||
} |
|||
this.statusList = arr; |
|||
this.titleList = titleArr; |
|||
this.tempTitleObj = r; |
|||
}, |
|||
data() { |
|||
return { |
|||
down: 'sl-down', |
|||
up: 'sl-up', |
|||
tabHeight: 50, |
|||
statusList: [], |
|||
selectedIndex: '', |
|||
titleList: [], |
|||
tempTitleObj: {} |
|||
}; |
|||
}, |
|||
methods: { |
|||
setTitle(obj) { |
|||
let list = obj |
|||
let titleArr = []; |
|||
for (let i = 0; i < list.length; i++) { |
|||
|
|||
if (list[i].reflexTitle && list[i].defaultSelectedIndex > -1) { |
|||
titleArr.push({ |
|||
'title': list[i].detailList[list[i].defaultSelectedIndex].title, |
|||
'key': list[i].key |
|||
}) |
|||
} else { |
|||
titleArr.push({ |
|||
'title': list[i].title, |
|||
'key': list[i].key |
|||
}) |
|||
} |
|||
|
|||
} |
|||
this.titleList = titleArr; |
|||
}, |
|||
refresh() { |
|||
let arr = []; |
|||
let titleArr = []; |
|||
let r = {}; |
|||
for (let i = 0; i < this.menuList.length; i++) { |
|||
arr.push({ |
|||
'isActive': false |
|||
}); |
|||
r[this.menuList[i].key] = this.menuList[i].title; |
|||
|
|||
if (this.menuList[i].reflexTitle && this.menuList[i].defaultSelectedIndex > -1) { |
|||
titleArr.push({ |
|||
'title': this.menuList[i].detailList[this.menuList[i].defaultSelectedIndex].title, |
|||
'key': this.menuList[i].key |
|||
}) |
|||
} else { |
|||
titleArr.push({ |
|||
'title': this.menuList[i].title, |
|||
'key': this.menuList[i].key |
|||
}) |
|||
} |
|||
|
|||
} |
|||
this.statusList = arr; |
|||
this.titleList = titleArr; |
|||
this.tempTitleObj = r; |
|||
}, |
|||
getMenuListTemp() { |
|||
let arr = this.menuList; |
|||
for (let i = 0; i < arr.length; i++) { |
|||
let item = arr[i]; |
|||
for (let j = 0; j < item.detailList.length; j++) { |
|||
let d_item = item.detailList[j]; |
|||
if (j == 0) { |
|||
d_item.isSelected = true |
|||
} else { |
|||
d_item.isSelected = false |
|||
} |
|||
} |
|||
} |
|||
return arr; |
|||
}, |
|||
// 重置所有选项,包括默认选项,并更新result |
|||
resetAllSelect(callback) { |
|||
this.$refs.slFilterView.resetAllSelect(function(e) { |
|||
callback(e); |
|||
}); |
|||
}, |
|||
// 重置选项为设置的默认值,并更新result |
|||
resetSelectToDefault(callback) { |
|||
this.$refs.slFilterView.resetSelectToDefault(function(e) { |
|||
callback(e); |
|||
}); |
|||
}, |
|||
resetSelect(val, index) { |
|||
this.$refs.slFilterView.reset1(val, index); |
|||
}, |
|||
resetMenuList(val) { |
|||
this.menuList = val; |
|||
this.$emit('update:menuList', val) |
|||
this.$forceUpdate(); |
|||
this.$refs.slFilterView.resetMenuList(val) |
|||
}, |
|||
showMenuClick(index) { |
|||
this.selectedIndex = index; |
|||
if (this.statusList[index].isActive == true) { |
|||
this.$refs.popupRef.close(); |
|||
this.statusList[index].isActive = false |
|||
} else { |
|||
this.menuTabClick(index); |
|||
this.$refs.popupRef.show() |
|||
} |
|||
}, |
|||
menuTabClick(index) { |
|||
this.$refs.slFilterView.menuTabClick(index); |
|||
for (let i = 0; i < this.statusList.length; i++) { |
|||
if (index == i) { |
|||
this.statusList[i].isActive = true; |
|||
} else { |
|||
this.statusList[i].isActive = false; |
|||
} |
|||
} |
|||
}, |
|||
filterResult(obj) { |
|||
let val = obj.result; |
|||
let titlesObj = obj.titles; |
|||
// 处理选项映射到菜单title |
|||
let tempTitle = ''; |
|||
let tempIndex = 0; |
|||
for (let i = 0; i < this.menuList[this.selectedIndex].detailList.length; i++) { |
|||
let item = this.menuList[this.selectedIndex].detailList[i]; |
|||
if (item.value == val[this.menuList[this.selectedIndex].key]) { |
|||
tempTitle = item.title; |
|||
tempIndex = i; |
|||
} |
|||
} |
|||
if (this.menuList[this.selectedIndex].reflexTitle) { |
|||
this.menuList[this.selectedIndex].defaultSelectedIndex = tempIndex; |
|||
this.titleList[this.selectedIndex].title = tempTitle; |
|||
} |
|||
|
|||
this.$refs.popupRef.close() |
|||
if (obj.isReset) { |
|||
|
|||
} else { |
|||
this.$emit("result", val) |
|||
} |
|||
|
|||
|
|||
}, |
|||
close() { |
|||
for (let i = 0; i < this.statusList.length; i++) { |
|||
this.statusList[i].isActive = false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
@import 'iconfont/iconfont.css'; |
|||
|
|||
.select-tab { |
|||
border-bottom: #F7F7F7 1px solid; |
|||
background-color: #FFFFFF; |
|||
display: flex; |
|||
width: 100%; |
|||
} |
|||
|
|||
.select-tab-fixed-top { |
|||
border-bottom: #F7F7F7 1px solid; |
|||
background-color: #FFFFFF; |
|||
display: flex; |
|||
width: 100%; |
|||
position: fixed; |
|||
/* #ifdef H5 */ |
|||
top: 44px; |
|||
/* #endif */ |
|||
/* #ifndef H5 */ |
|||
top: 0; |
|||
/* #endif */ |
|||
|
|||
} |
|||
|
|||
.arrows { |
|||
margin-left: 5px; |
|||
} |
|||
|
|||
.select-tab .select-tab-item, |
|||
.select-tab-fixed-top .select-tab-item { |
|||
display: flex; |
|||
padding-left: 20rpx; |
|||
padding-right: 20rpx; |
|||
align-items: center; |
|||
} |
|||
|
|||
.select-tab .select-tab-item text, |
|||
.select-tab-fixed-top .select-tab-item text { |
|||
color: #666666; |
|||
font-size: 14px; |
|||
} |
|||
</style> |
@ -0,0 +1,173 @@ |
|||
<template> |
|||
<view class='refreshBox' :style="isTranform"> |
|||
<view class='refresh' :style="isZoom" :class="isEnd==2?'animationSmall':''"> |
|||
<image class='refreshWord' src='../../static/img/public/shuaxin.png' v-if="isEnd == 0"></image> |
|||
<view class='refreshCirle animation' v-if="isEnd == 1"></view> |
|||
<image class='iconYes' src='../../static/img/public/icon-yes.png' v-if="isEnd==2"></image> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'refresh', |
|||
props: { |
|||
isTop: { |
|||
type: Number, |
|||
default: 1 |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
isTranf: 0, |
|||
touchStart: '', |
|||
touchMove: '', |
|||
isEnd: 0 |
|||
}; |
|||
}, |
|||
|
|||
methods: { |
|||
refreshStart(e) { |
|||
if (this.isEnd == 0 && this.isTop == 1) { |
|||
this.touchStart = e.changedTouches[0].pageY |
|||
} |
|||
}, |
|||
refreshMove(e) { |
|||
if (this.isEnd == 0 && this.isTop == 1) { |
|||
var touchStart = this.touchStart, |
|||
oldMove = this.touchMove, |
|||
newMove = e.changedTouches[0].pageY |
|||
if (touchStart <= newMove) { |
|||
var isTranf = touchStart > newMove ? 0 : newMove - touchStart |
|||
this.isTranf = isTranf |
|||
this.touchMove = e.changedTouches[0].pageY |
|||
} |
|||
} else { |
|||
this.isTranf = 0 |
|||
this.isEnd = 0 |
|||
this.touchStart = 9999 |
|||
} |
|||
}, |
|||
refreshEnd(e) { |
|||
var that = this |
|||
if (this.isEnd == 0 && this.isTop == 1) { |
|||
if (this.isTranf >= 90) { |
|||
this.isTranf = 125 |
|||
this.isEnd = 1 |
|||
this.$emit('isRefresh'); |
|||
} else { |
|||
this.isTranf = 0 |
|||
} |
|||
} |
|||
}, |
|||
endAfter() { |
|||
this.isEnd = 2 |
|||
setTimeout(() => { |
|||
this.isTranf = 0 |
|||
this.isEnd = 0 |
|||
}, 600) |
|||
} |
|||
}, |
|||
computed: { |
|||
isTranform() { |
|||
var isTranf = this.isTranf > 150 ? 150 : this.isTranf |
|||
var isTemp = `transform: translateY(${isTranf-100}px);` |
|||
return isTemp |
|||
}, |
|||
isZoom() { |
|||
var isTranf = this.isTranf > 125 ? 125 : this.isTranf |
|||
var isTemp = `zoom:${isTranf/125};` |
|||
return isTemp |
|||
} |
|||
} |
|||
|
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.refreshBox { |
|||
margin: 0 auto; |
|||
width: 100%; |
|||
height: 100upx; |
|||
overflow: hidden; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
max-height: 300upx; |
|||
position: fixed; |
|||
z-index: 999; |
|||
top: 0; |
|||
left: 0; |
|||
transform: translateY(-100upx); |
|||
} |
|||
|
|||
.animationSmall { |
|||
animation: small 1.1s both; |
|||
} |
|||
|
|||
@keyframes small { |
|||
0% { |
|||
transform: scale(1) |
|||
} |
|||
|
|||
20% { |
|||
transform: scale(1.4) |
|||
} |
|||
|
|||
100% { |
|||
transform: scale(0) |
|||
} |
|||
} |
|||
|
|||
.refreshWord { |
|||
width: 26upx; |
|||
height: 26upx; |
|||
border-radius: 26upx; |
|||
} |
|||
|
|||
.refresh { |
|||
min-width: 50upx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 50upx; |
|||
background: #FFFFFF; |
|||
box-shadow: 0 0 16upx 0 rgba(0, 0, 0, 0.10); |
|||
border-radius: 50upx; |
|||
} |
|||
|
|||
.refreshCirle { |
|||
width: 26upx; |
|||
height: 26upx; |
|||
border-radius: 50%; |
|||
display: inline-block; |
|||
position: relative; |
|||
border: 6upx solid black; |
|||
border-bottom-color: transparent; |
|||
border-top-color: transparent; |
|||
} |
|||
|
|||
.animation { |
|||
animation: rotate 1.1s infinite; |
|||
animation-timing-function: cubic-bezier(0.3, 1.65, 0.7, -0.65); |
|||
} |
|||
|
|||
@keyframes rotate { |
|||
0% { |
|||
transform: rotate(0deg); |
|||
} |
|||
|
|||
100% { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
|||
|
|||
.true { |
|||
color: black; |
|||
} |
|||
|
|||
.iconYes { |
|||
width: 34upx; |
|||
height: 34upx; |
|||
} |
|||
</style> |
@ -0,0 +1,112 @@ |
|||
<template> |
|||
<view class="navTabBox"> |
|||
<view class="longTab"> |
|||
<scroll-view scroll-x="true" style="white-space: nowrap; display: flex" scroll-with-animation |
|||
:scroll-left="tabLeft"> |
|||
<view class="longItem" :style='"width:"+isWidth+"px"' :data-index="index" |
|||
:class="index===tabClick?'click':''" v-for="(item,index) in tabTitle" :key="index" :id="'id'+index" |
|||
@click="longClick(index)">{{item}} |
|||
<text v-if="tabNum[index]!=0 && tabNum.length == tabTitle.length && tabNum[index] != undefined" |
|||
style="color: #FFFFFF;font-size: 10rpx;background-color: red; width: 20rpx; height: 20rxp; border-radius: 20rpx;padding-left: 2rpx;padding-right: 2rpx;">{{tabNum[index]}}</text> |
|||
</view> |
|||
<view class="underlineBox" :style='"transform:translateX("+isLeft+"px);width:"+isWidth+"px"'> |
|||
<view class="underline"></view> |
|||
</view> |
|||
</scroll-view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'navTab', |
|||
props: { |
|||
tabTitle: { |
|||
type: Array, |
|||
default: [] |
|||
}, |
|||
tabNum: { |
|||
type: Array, |
|||
default: [] |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
tabClick: 0, //导航栏被点击 |
|||
isLeft: 0, //导航栏下划线位置 |
|||
isWidth: 0, //每个导航栏占位 |
|||
tabLeft: 0, |
|||
current: 0 //当前的tab |
|||
}; |
|||
}, |
|||
created() { |
|||
var that = this |
|||
// 获取设备宽度 |
|||
uni.getSystemInfo({ |
|||
success(e) { |
|||
if (that.tabTitle.length <= 5) { |
|||
that.isWidth = e.windowWidth / that.tabTitle.length //宽度除以导航标题个数=一个导航所占宽度 |
|||
} else { |
|||
that.isWidth = e.windowWidth / 5 |
|||
} |
|||
} |
|||
}) |
|||
}, |
|||
methods: { |
|||
// 导航栏点击 |
|||
longClick(index) { |
|||
|
|||
if (this.tabTitle.length > 5) { |
|||
var tempIndex = index - 2; |
|||
tempIndex = tempIndex <= 0 ? 0 : tempIndex; |
|||
this.tabLeft = (index - 2) * this.isWidth //设置下划线位置 |
|||
} |
|||
this.tabClick = index //设置导航点击了哪一个 |
|||
this.isLeft = index * this.isWidth //设置下划线位置 |
|||
|
|||
if (this.current != index) { |
|||
this.$emit('onTabClick', index); //设置swiper的第几页 |
|||
this.current = index |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.navTabBox { |
|||
width: 100vw; |
|||
color: rgba(255, 255, 255, 0.50); |
|||
|
|||
.click { |
|||
color: white; |
|||
} |
|||
|
|||
.longTab { |
|||
width: 100%; |
|||
|
|||
.longItem { |
|||
height: 45px; |
|||
display: inline-block; |
|||
line-height: 45px; |
|||
text-align: center; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.underlineBox { |
|||
height: 3px; |
|||
width: 20%; |
|||
display: flex; |
|||
align-content: center; |
|||
justify-content: center; |
|||
transition: .5s; |
|||
|
|||
.underline { |
|||
width: 42upx; |
|||
height: 2px; |
|||
background-color: white; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,45 @@ |
|||
// #ifdef H5
|
|||
export default { |
|||
name: 'Keypress', |
|||
props: { |
|||
disable: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
mounted () { |
|||
const keyNames = { |
|||
esc: ['Esc', 'Escape'], |
|||
tab: 'Tab', |
|||
enter: 'Enter', |
|||
space: [' ', 'Spacebar'], |
|||
up: ['Up', 'ArrowUp'], |
|||
left: ['Left', 'ArrowLeft'], |
|||
right: ['Right', 'ArrowRight'], |
|||
down: ['Down', 'ArrowDown'], |
|||
delete: ['Backspace', 'Delete', 'Del'] |
|||
} |
|||
const listener = ($event) => { |
|||
if (this.disable) { |
|||
return |
|||
} |
|||
const keyName = Object.keys(keyNames).find(key => { |
|||
const keyName = $event.key |
|||
const value = keyNames[key] |
|||
return value === keyName || (Array.isArray(value) && value.includes(keyName)) |
|||
}) |
|||
if (keyName) { |
|||
// 避免和其他按键事件冲突
|
|||
setTimeout(() => { |
|||
this.$emit(keyName, {}) |
|||
}, 0) |
|||
} |
|||
} |
|||
document.addEventListener('keyup', listener) |
|||
this.$once('hook:beforeDestroy', () => { |
|||
document.removeEventListener('keyup', listener) |
|||
}) |
|||
}, |
|||
render: () => {} |
|||
} |
|||
// #endif
|
@ -0,0 +1,804 @@ |
|||
<template> |
|||
<view class="uni-datetime-picker"> |
|||
<view @click="initTimePicker"> |
|||
<slot> |
|||
<view class="uni-datetime-picker-timebox uni-datetime-picker-flex" |
|||
:class="{'uni-datetime-picker-disabled': disabled}"> |
|||
{{time}} |
|||
<view v-if="!time" class="uni-datetime-picker-time"> |
|||
选择{{title}} |
|||
</view> |
|||
<view class="uni-datetime-picker-down-arrow"></view> |
|||
</view> |
|||
</slot> |
|||
</view> |
|||
<view v-if="visible" class="uni-datetime-picker-mask" @click="tiggerTimePicker"></view> |
|||
<view v-if="visible" class="uni-datetime-picker-popup"> |
|||
<view class="uni-title"> |
|||
设置{{title}} |
|||
</view> |
|||
<picker-view v-show="dateShow" class="uni-datetime-picker-view" :indicator-style="indicatorStyle" |
|||
:value="ymd" @change="bindDateChange"> |
|||
<picker-view-column class="uni-datetime-picker-hyphen"> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in years" :key="index">{{item}}</view> |
|||
</picker-view-column> |
|||
<picker-view-column class="uni-datetime-picker-hyphen"> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in months" :key="index"> |
|||
{{item < 10 ? ((item+"").startsWith("0")? item:('0' +item)) : item}} |
|||
</view> |
|||
</picker-view-column> |
|||
<picker-view-column> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in days" :key="index"> |
|||
{{item < 10 ? '0' + item : item}} |
|||
</view> |
|||
</picker-view-column> |
|||
</picker-view> |
|||
<picker-view v-show="timeShow" class="uni-datetime-picker-view" :indicator-style="indicatorStyle" |
|||
:value="hms" @change="bindTimeChange"> |
|||
<picker-view-column class="uni-datetime-picker-colon"> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in hours" :key="index"> |
|||
{{item < 10 ? '0' + item : item}} |
|||
</view> |
|||
</picker-view-column> |
|||
<picker-view-column class="uni-datetime-picker-colon"> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in minutes" :key="index"> |
|||
{{item < 10 ? '0' + item : item}} |
|||
</view> |
|||
</picker-view-column> |
|||
<picker-view-column> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in seconds" :key="index"> |
|||
{{item < 10 ? '0' + item : item}} |
|||
</view> |
|||
</picker-view-column> |
|||
</picker-view> |
|||
<view class="uni-datetime-picker-btn"> |
|||
<view class="" @click="clearTime">清空</view> |
|||
<view class="uni-datetime-picker-btn-group"> |
|||
<view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">取消</view> |
|||
<view class="" @click="setTime">确定</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
/** |
|||
* DatetimePicker 时间选择器 |
|||
* @description 可以同时选择日期和时间的选择器 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx |
|||
* @property {String} type = [datetime | date | time] 显示模式 |
|||
* @property {Boolean} multiple = [true|false] 是否多选 |
|||
* @property {String|Number} value 默认值 |
|||
* @property {String|Number} start 起始日期或时间 |
|||
* @property {String|Number} end 起始日期或时间 |
|||
* @property {String} return-type = [timestamp | string] |
|||
* @event {Function} change 选中发生变化触发 |
|||
*/ |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
indicatorStyle: `height: 50px;`, |
|||
visible: false, |
|||
dateShow: true, |
|||
timeShow: true, |
|||
title: '日期和时间', |
|||
// 输入框当前时间 |
|||
time: '', |
|||
// 当前的年月日时分秒 |
|||
year: 1900, |
|||
month: 0, |
|||
day: 0, |
|||
hour: 0, |
|||
minute: 0, |
|||
second: 0, |
|||
// 起始时间 |
|||
startYear: 1920, |
|||
startMonth: 1, |
|||
startDay: 1, |
|||
startHour: 0, |
|||
startMinute: 0, |
|||
startSecond: 0, |
|||
// 结束时间 |
|||
endYear: 2120, |
|||
endMonth: 12, |
|||
endDay: 31, |
|||
endHour: 23, |
|||
endMinute: 59, |
|||
endSecond: 59, |
|||
} |
|||
}, |
|||
props: { |
|||
type: { |
|||
type: String, |
|||
default: 'datetime' |
|||
}, |
|||
value: { |
|||
type: [String, Number], |
|||
default: '' |
|||
}, |
|||
start: { |
|||
type: [Number, String], |
|||
default: '' |
|||
}, |
|||
end: { |
|||
type: [Number, String], |
|||
default: '' |
|||
}, |
|||
returnType: { |
|||
type: String, |
|||
default: 'string' |
|||
}, |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
watch: { |
|||
value: { |
|||
handler(newVal, oldVal) { |
|||
if (newVal) { |
|||
this.parseValue(this.fixIosDateFormat(newVal)) //兼容 iOS、safari 日期格式 |
|||
this.initTime() |
|||
} else { |
|||
this.parseValue(Date.now()) |
|||
} |
|||
}, |
|||
immediate: true |
|||
}, |
|||
type: { |
|||
handler(newValue) { |
|||
if (newValue === 'date') { |
|||
this.dateShow = true |
|||
this.timeShow = false |
|||
this.title = '日期' |
|||
} else if (newValue === 'time') { |
|||
this.dateShow = false |
|||
this.timeShow = true |
|||
this.title = '时间' |
|||
} else { |
|||
this.dateShow = true |
|||
this.timeShow = true |
|||
this.title = '日期和时间' |
|||
} |
|||
}, |
|||
immediate: true |
|||
}, |
|||
start: { |
|||
handler(newVal) { |
|||
this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'start') //兼容 iOS、safari 日期格式 |
|||
}, |
|||
immediate: true |
|||
}, |
|||
end: { |
|||
handler(newVal) { |
|||
this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'end') //兼容 iOS、safari 日期格式 |
|||
}, |
|||
immediate: true |
|||
}, |
|||
|
|||
// 月、日、时、分、秒可选范围变化后,检查当前值是否在范围内,不在则当前值重置为可选范围第一项 |
|||
months(newVal) { |
|||
this.checkValue('month', this.month, newVal) |
|||
}, |
|||
days(newVal) { |
|||
this.checkValue('day', this.day, newVal) |
|||
}, |
|||
hours(newVal) { |
|||
this.checkValue('hour', this.hour, newVal) |
|||
}, |
|||
minutes(newVal) { |
|||
this.checkValue('minute', this.minute, newVal) |
|||
}, |
|||
seconds(newVal) { |
|||
this.checkValue('second', this.second, newVal) |
|||
} |
|||
}, |
|||
created() { |
|||
this.form = this.getForm('uniForms') |
|||
this.formItem = this.getForm('uniFormsItem') |
|||
|
|||
if (this.formItem) { |
|||
if (this.formItem.name) { |
|||
this.rename = this.formItem.name |
|||
this.form.inputChildrens.push(this) |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
// 当前年、月、日、时、分、秒选择范围 |
|||
years() { |
|||
return this.getCurrentRange('year') |
|||
}, |
|||
|
|||
months() { |
|||
return this.getCurrentRange('month') |
|||
}, |
|||
|
|||
days() { |
|||
return this.getCurrentRange('day') |
|||
}, |
|||
|
|||
hours() { |
|||
return this.getCurrentRange('hour') |
|||
}, |
|||
|
|||
minutes() { |
|||
return this.getCurrentRange('minute') |
|||
}, |
|||
|
|||
seconds() { |
|||
return this.getCurrentRange('second') |
|||
}, |
|||
|
|||
// picker 当前值数组 |
|||
ymd() { |
|||
return [this.year - this.minYear, this.month - this.minMonth, this.day - this.minDay] |
|||
}, |
|||
hms() { |
|||
return [this.hour - this.minHour, this.minute - this.minMinute, this.second - this.minSecond] |
|||
}, |
|||
|
|||
// 当前 date 是 start |
|||
currentDateIsStart() { |
|||
return this.year === this.startYear && this.month === this.startMonth && this.day === this.startDay |
|||
}, |
|||
|
|||
// 当前 date 是 end |
|||
currentDateIsEnd() { |
|||
return this.year === this.endYear && this.month === this.endMonth && this.day === this.endDay |
|||
}, |
|||
|
|||
// 当前年、月、日、时、分、秒的最小值和最大值 |
|||
minYear() { |
|||
return this.startYear |
|||
}, |
|||
maxYear() { |
|||
return this.endYear |
|||
}, |
|||
minMonth() { |
|||
if (this.year === this.startYear) { |
|||
return this.startMonth |
|||
} else { |
|||
return 1 |
|||
} |
|||
}, |
|||
maxMonth() { |
|||
if (this.year === this.endYear) { |
|||
return this.endMonth |
|||
} else { |
|||
return 12 |
|||
} |
|||
}, |
|||
minDay() { |
|||
if (this.year === this.startYear && this.month === this.startMonth) { |
|||
return this.startDay |
|||
} else { |
|||
return 1 |
|||
} |
|||
}, |
|||
maxDay() { |
|||
if (this.year === this.endYear && this.month === this.endMonth) { |
|||
return this.endDay |
|||
} else { |
|||
return this.daysInMonth(this.year, this.month) |
|||
} |
|||
}, |
|||
minHour() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsStart) { |
|||
return this.startHour |
|||
} else { |
|||
return 0 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
return this.startHour |
|||
} |
|||
}, |
|||
maxHour() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsEnd) { |
|||
return this.endHour |
|||
} else { |
|||
return 23 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
return this.endHour |
|||
} |
|||
}, |
|||
minMinute() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsStart && this.hour === this.startHour) { |
|||
return this.startMinute |
|||
} else { |
|||
return 0 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
if (this.hour === this.startHour) { |
|||
return this.startMinute |
|||
} else { |
|||
return 0 |
|||
} |
|||
} |
|||
}, |
|||
maxMinute() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsEnd && this.hour === this.startHour) { |
|||
return this.endMinute |
|||
} else { |
|||
return 59 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
if (this.hour === this.endHour) { |
|||
return this.endMinute |
|||
} else { |
|||
return 59 |
|||
} |
|||
} |
|||
}, |
|||
minSecond() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsStart && this.hour === this.startHour && this.minute === this.startMinute) { |
|||
return this.startSecond |
|||
} else { |
|||
return 0 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
if (this.hour === this.startHour && this.minute === this.startMinute) { |
|||
return this.startSecond |
|||
} else { |
|||
return 0 |
|||
} |
|||
} |
|||
}, |
|||
maxSecond() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsEnd && this.hour === this.startHour && this.minute === this.endMinute) { |
|||
return this.endSecond |
|||
} else { |
|||
return 59 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
if (this.hour === this.endHour && this.minute === this.endMinute) { |
|||
return this.endSecond |
|||
} else { |
|||
return 59 |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* 获取父元素实例 |
|||
*/ |
|||
getForm(name = 'uniForms') { |
|||
let parent = this.$parent; |
|||
let parentName = parent.$options.name; |
|||
while (parentName !== name) { |
|||
parent = parent.$parent; |
|||
if (!parent) return false |
|||
parentName = parent.$options.name; |
|||
} |
|||
return parent; |
|||
}, |
|||
|
|||
/** |
|||
* 解析时分秒字符串,例如:00:00:00 |
|||
* @param {String} timeString |
|||
*/ |
|||
parseTimeType(timeString) { |
|||
if (timeString) { |
|||
let timeArr = timeString.split(':') |
|||
this.hour = Number(timeArr[0]) |
|||
this.minute = Number(timeArr[1]) |
|||
this.second = Number(timeArr[2]) |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 解析选择器初始值,类型可以是字符串、时间戳,例如:2000-10-02、'08:30:00'、 1610695109000 |
|||
* @param {String | Number} datetime |
|||
*/ |
|||
initPickerValue(datetime) { |
|||
let defaultValue = null |
|||
if (datetime) { |
|||
defaultValue = this.compareValueWithStartAndEnd(datetime, this.start, this.end) |
|||
} else { |
|||
defaultValue = Date.now() |
|||
defaultValue = this.compareValueWithStartAndEnd(defaultValue, this.start, this.end) |
|||
} |
|||
this.parseValue(defaultValue) |
|||
}, |
|||
|
|||
/** |
|||
* 初始值规则: |
|||
* - 用户设置初始值 value |
|||
* - 设置了起始时间 start、终止时间 end,并 start < value < end,初始值为 value, 否则初始值为 start |
|||
* - 只设置了起始时间 start,并 start < value,初始值为 value,否则初始值为 start |
|||
* - 只设置了终止时间 end,并 value < end,初始值为 value,否则初始值为 end |
|||
* - 无起始终止时间,则初始值为 value |
|||
* - 无初始值 value,则初始值为当前本地时间 Date.now() |
|||
* @param {Object} value |
|||
* @param {Object} dateBase |
|||
*/ |
|||
compareValueWithStartAndEnd(value, start, end) { |
|||
let winner = null |
|||
value = this.superTimeStamp(value) |
|||
start = this.superTimeStamp(start) |
|||
end = this.superTimeStamp(end) |
|||
|
|||
if (start && end) { |
|||
if (value < start) { |
|||
winner = new Date(start) |
|||
} else if (value > end) { |
|||
winner = new Date(end) |
|||
} else { |
|||
winner = new Date(value) |
|||
} |
|||
} else if (start && !end) { |
|||
winner = start <= value ? new Date(value) : new Date(start) |
|||
} else if (!start && end) { |
|||
winner = value <= end ? new Date(value) : new Date(end) |
|||
} else { |
|||
winner = new Date(value) |
|||
} |
|||
|
|||
return winner |
|||
}, |
|||
|
|||
/** |
|||
* 转换为可比较的时间戳,接受日期、时分秒、时间戳 |
|||
* @param {Object} value |
|||
*/ |
|||
superTimeStamp(value) { |
|||
let dateBase = '' |
|||
if (this.type === 'time' && value && typeof value === 'string') { |
|||
const now = new Date() |
|||
const year = now.getFullYear() |
|||
const month = now.getMonth() + 1 |
|||
const day = now.getDate() |
|||
dateBase = year + '/' + month + '/' + day + ' ' |
|||
} |
|||
if (Number(value) && typeof value !== NaN) { |
|||
value = parseInt(value) |
|||
dateBase = 0 |
|||
} |
|||
return this.createTimeStamp(dateBase + value) |
|||
}, |
|||
|
|||
/** |
|||
* 解析默认值 value,字符串、时间戳 |
|||
* @param {Object} defaultTime |
|||
*/ |
|||
parseValue(value) { |
|||
if (!value) return |
|||
if (this.type === 'time' && typeof value === "string") { |
|||
this.parseTimeType(value) |
|||
} else { |
|||
let defaultDate = null |
|||
defaultDate = new Date(value) |
|||
if (this.type !== 'time') { |
|||
this.year = defaultDate.getFullYear() |
|||
this.month = defaultDate.getMonth() + 1 |
|||
this.day = defaultDate.getDate() |
|||
} |
|||
if (this.type !== 'date') { |
|||
this.hour = defaultDate.getHours() |
|||
this.minute = defaultDate.getMinutes() |
|||
this.second = defaultDate.getSeconds() |
|||
} |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 解析可选择时间范围 start、end,年月日字符串、时间戳 |
|||
* @param {Object} defaultTime |
|||
*/ |
|||
parseDatetimeRange(point, pointType) { |
|||
if (point && this.type === 'time') { |
|||
const pointArr = point.split(':') |
|||
this[pointType + 'Hour'] = Number(pointArr[0]) |
|||
this[pointType + 'Minute'] = Number(pointArr[1]) |
|||
this[pointType + 'Second'] = Number(pointArr[2]) |
|||
} else { |
|||
if (!point) { |
|||
pointType === 'start' ? this.startYear = this.year - 60 : this.endYear = this.year + 60 |
|||
return |
|||
} |
|||
if (Number(point) && Number(point) !== NaN) { |
|||
point = parseInt(point) |
|||
} |
|||
// datetime 的 end 没有时分秒, 则不限制 |
|||
const hasTime = /[0-9]:[0-9]/ |
|||
if (this.type === 'datetime' && pointType === 'end' && typeof point === 'string' && !hasTime.test( |
|||
point)) { |
|||
point = point + ' 23:59:59' |
|||
} |
|||
// 2016/09/19 17:04:26:719 |
|||
const split = point.split(" ") |
|||
const dataStr = split[0] |
|||
const timeStr = split[1] |
|||
|
|||
this[pointType + 'Year'] = dataStr.split("/")[0] |
|||
this[pointType + 'Month'] = dataStr.split("/")[1] |
|||
this[pointType + 'Day'] = dataStr.split("/")[2] |
|||
if (this.type === 'datetime') { |
|||
this[pointType + 'Hour'] = timeStr.split(":")[0] |
|||
this[pointType + 'Minute'] = timeStr.split(":")[1] |
|||
this[pointType + 'Second'] = timeStr.split(":")[2] |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// 获取 年、月、日、时、分、秒 当前可选范围 |
|||
getCurrentRange(value) { |
|||
const range = [] |
|||
for (let i = this['min' + this.capitalize(value)]; i <= this['max' + this.capitalize(value)]; i++) { |
|||
range.push(i) |
|||
} |
|||
return range |
|||
}, |
|||
|
|||
// 字符串首字母大写 |
|||
capitalize(str) { |
|||
return str.charAt(0).toUpperCase() + str.slice(1) |
|||
}, |
|||
|
|||
// 检查当前值是否在范围内,不在则当前值重置为可选范围第一项 |
|||
checkValue(name, value, values) { |
|||
if (values.indexOf(value) === -1) { |
|||
this[name] = values[0] |
|||
} |
|||
}, |
|||
|
|||
// 每个月的实际天数 |
|||
daysInMonth(year, month) { // Use 1 for January, 2 for February, etc. |
|||
return new Date(year, month, 0).getDate(); |
|||
}, |
|||
|
|||
//兼容 iOS、safari 日期格式 |
|||
fixIosDateFormat(value) { |
|||
if (typeof value === 'string') { |
|||
value = value.replace(/-/g, '/') |
|||
} |
|||
return value |
|||
}, |
|||
|
|||
/** |
|||
* 生成时间戳 |
|||
* @param {Object} time |
|||
*/ |
|||
createTimeStamp(time) { |
|||
if (!time) return |
|||
if (typeof time === "number") { |
|||
return time |
|||
} else { |
|||
time = time.replace(/-/g, '/') |
|||
if (this.type === 'date') { |
|||
time = time + ' ' + '00:00:00' |
|||
} |
|||
return Date.parse(time) |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 生成日期或时间的字符串 |
|||
*/ |
|||
createDomSting() { |
|||
const yymmdd = this.year + |
|||
'-' + |
|||
(this.month < 10 ? '0' + this.month : this.month) + |
|||
'-' + |
|||
(this.day < 10 ? '0' + this.day : this.day) |
|||
|
|||
const hhmmss = (this.hour < 10 ? '0' + this.hour : this.hour) + |
|||
':' + |
|||
(this.minute < 10 ? '0' + this.minute : this.minute) + |
|||
':' + |
|||
(this.second < 10 ? '0' + this.second : this.second) |
|||
|
|||
if (this.type === 'date') { |
|||
return yymmdd |
|||
} else if (this.type === 'time') { |
|||
return hhmmss |
|||
} else { |
|||
return yymmdd + ' ' + hhmmss |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 初始化返回值,并抛出 change 事件 |
|||
*/ |
|||
initTime() { |
|||
this.time = this.createDomSting() |
|||
if (this.returnType === 'timestamp' && this.type !== 'time') { |
|||
this.formItem && this.formItem.setValue(this.createTimeStamp(this.time)) |
|||
this.$emit('change', this.createTimeStamp(this.time)) |
|||
this.$emit('input', this.createTimeStamp(this.time)) |
|||
} else { |
|||
this.formItem && this.formItem.setValue(this.time) |
|||
this.$emit('change', this.time) |
|||
this.$emit('input', this.time) |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 用户选择日期或时间更新 data |
|||
* @param {Object} e |
|||
*/ |
|||
bindDateChange(e) { |
|||
const val = e.detail.value |
|||
this.year = this.years[val[0]] |
|||
this.month = this.months[val[1]] |
|||
this.day = this.days[val[2]] |
|||
}, |
|||
bindTimeChange(e) { |
|||
const val = e.detail.value |
|||
this.hour = this.hours[val[0]] |
|||
this.minute = this.minutes[val[1]] |
|||
this.second = this.seconds[val[2]] |
|||
}, |
|||
|
|||
/** |
|||
* 初始化弹出层 |
|||
*/ |
|||
initTimePicker() { |
|||
if (this.disabled) return |
|||
const value = this.fixIosDateFormat(this.value) |
|||
this.initPickerValue(value) |
|||
this.visible = !this.visible |
|||
}, |
|||
|
|||
/** |
|||
* 触发或关闭弹框 |
|||
*/ |
|||
tiggerTimePicker() { |
|||
this.visible = !this.visible |
|||
}, |
|||
|
|||
/** |
|||
* 用户点击“清空”按钮,清空当前值 |
|||
*/ |
|||
clearTime() { |
|||
this.time = '' |
|||
this.formItem && this.formItem.setValue(this.time) |
|||
this.$emit('change', this.time) |
|||
this.$emit('input', this.time) |
|||
this.tiggerTimePicker() |
|||
}, |
|||
|
|||
/** |
|||
* 用户点击“确定”按钮 |
|||
*/ |
|||
setTime() { |
|||
this.initTime() |
|||
this.tiggerTimePicker() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.uni-datetime-picker-view { |
|||
width: 100%; |
|||
height: 130px; |
|||
margin-top: 30px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.uni-datetime-picker-item { |
|||
line-height: 50px; |
|||
text-align: center; |
|||
} |
|||
|
|||
.uni-datetime-picker-btn { |
|||
margin-top: 60px; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
color: #007AFF; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.uni-datetime-picker-btn-group { |
|||
display: flex; |
|||
} |
|||
|
|||
.uni-datetime-picker-cancel { |
|||
margin-right: 30px; |
|||
} |
|||
|
|||
.uni-datetime-picker-mask { |
|||
position: fixed; |
|||
bottom: 0px; |
|||
top: 0px; |
|||
left: 0px; |
|||
right: 0px; |
|||
background-color: rgba(0, 0, 0, 0.4); |
|||
transition-duration: 0.3s; |
|||
z-index: 998; |
|||
} |
|||
|
|||
.uni-datetime-picker-popup { |
|||
border-radius: 8px; |
|||
padding: 30px; |
|||
width: 270px; |
|||
background-color: #fff; |
|||
position: fixed; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translate(-50%, -50%); |
|||
transition-duration: 0.3s; |
|||
z-index: 999; |
|||
} |
|||
|
|||
.uni-datetime-picker-time { |
|||
color: grey; |
|||
} |
|||
|
|||
.uni-datetime-picker-colon::after { |
|||
content: ':'; |
|||
position: absolute; |
|||
top: 53px; |
|||
right: 0; |
|||
} |
|||
|
|||
.uni-datetime-picker-hyphen::after { |
|||
content: '-'; |
|||
position: absolute; |
|||
top: 53px; |
|||
right: -2px; |
|||
} |
|||
|
|||
.uni-datetime-picker-timebox { |
|||
border: 1px solid #E5E5E5; |
|||
border-radius: 5px; |
|||
padding: 7px 10px; |
|||
box-sizing: border-box; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
// 下箭头 |
|||
.uni-datetime-picker-down-arrow { |
|||
display: inline-block; |
|||
position: relative; |
|||
width: 20px; |
|||
height: 15px; |
|||
} |
|||
|
|||
.uni-datetime-picker-down-arrow::after { |
|||
display: inline-block; |
|||
content: " "; |
|||
height: 9px; |
|||
width: 9px; |
|||
border-width: 0 1px 1px 0; |
|||
border-color: #E5E5E5; |
|||
border-style: solid; |
|||
transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0); |
|||
transform-origin: center; |
|||
transition: transform .3s; |
|||
position: absolute; |
|||
top: 50%; |
|||
right: 5px; |
|||
margin-top: -5px; |
|||
} |
|||
|
|||
.uni-datetime-picker-flex { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.uni-datetime-picker-disabled { |
|||
opacity: 0.4; |
|||
/* #ifdef H5 */ |
|||
cursor: not-allowed !important; |
|||
/* #endif */ |
|||
} |
|||
</style> |
@ -0,0 +1,243 @@ |
|||
<template> |
|||
<view class="uni-popup-dialog"> |
|||
<view class="uni-dialog-title"> |
|||
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{title}}</text> |
|||
</view> |
|||
<view class="uni-dialog-content"> |
|||
<text class="uni-dialog-content-text" v-if="mode === 'base'">{{content}}</text> |
|||
<input v-else class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholder" :focus="focus"> |
|||
</view> |
|||
<view class="uni-dialog-button-group"> |
|||
<view class="uni-dialog-button" @click="close"> |
|||
<text class="uni-dialog-button-text">取消</text> |
|||
</view> |
|||
<view class="uni-dialog-button uni-border-left" @click="onOk"> |
|||
<text class="uni-dialog-button-text uni-button-color">确定</text> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
/** |
|||
* PopUp 弹出层-对话框样式 |
|||
* @description 弹出层-对话框样式 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329 |
|||
* @property {String} value input 模式下的默认值 |
|||
* @property {String} placeholder input 模式下输入提示 |
|||
* @property {String} type = [success|warning|info|error] 主题样式 |
|||
* @value success 成功 |
|||
* @value warning 提示 |
|||
* @value info 消息 |
|||
* @value error 错误 |
|||
* @property {String} mode = [base|input] 模式、 |
|||
* @value base 基础对话框 |
|||
* @value input 可输入对话框 |
|||
* @property {String} content 对话框内容 |
|||
* @property {Boolean} beforeClose 是否拦截取消事件 |
|||
* @event {Function} confirm 点击确认按钮触发 |
|||
* @event {Function} close 点击取消按钮触发 |
|||
*/ |
|||
|
|||
export default { |
|||
name: "uniPopupDialog", |
|||
props: { |
|||
value: { |
|||
type: [String, Number], |
|||
default: '' |
|||
}, |
|||
placeholder: { |
|||
type: [String, Number], |
|||
default: '请输入内容' |
|||
}, |
|||
/** |
|||
* 对话框主题 success/warning/info/error 默认 success |
|||
*/ |
|||
type: { |
|||
type: String, |
|||
default: 'error' |
|||
}, |
|||
/** |
|||
* 对话框模式 base/input |
|||
*/ |
|||
mode: { |
|||
type: String, |
|||
default: 'base' |
|||
}, |
|||
/** |
|||
* 对话框标题 |
|||
*/ |
|||
title: { |
|||
type: String, |
|||
default: '提示' |
|||
}, |
|||
/** |
|||
* 对话框内容 |
|||
*/ |
|||
content: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
/** |
|||
* 拦截取消事件 ,如果拦截取消事件,必须监听close事件,执行 done() |
|||
*/ |
|||
beforeClose: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
dialogType: 'error', |
|||
focus: false, |
|||
val: "" |
|||
} |
|||
}, |
|||
inject: ['popup'], |
|||
watch: { |
|||
type(val) { |
|||
this.dialogType = val |
|||
}, |
|||
mode(val) { |
|||
if (val === 'input') { |
|||
this.dialogType = 'info' |
|||
} |
|||
}, |
|||
value(val) { |
|||
this.val = val |
|||
} |
|||
}, |
|||
created() { |
|||
// 对话框遮罩不可点击 |
|||
this.popup.mkclick = false |
|||
if (this.mode === 'input') { |
|||
this.dialogType = 'info' |
|||
this.val = this.value |
|||
} else { |
|||
this.dialogType = this.type |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.focus = true |
|||
}, |
|||
methods: { |
|||
/** |
|||
* 点击确认按钮 |
|||
*/ |
|||
onOk() { |
|||
this.$emit('confirm', () => { |
|||
this.popup.close() |
|||
if (this.mode === 'input') this.val = this.value |
|||
}, this.mode === 'input' ? this.val : '') |
|||
}, |
|||
/** |
|||
* 点击取消按钮 |
|||
*/ |
|||
close() { |
|||
if (this.beforeClose) { |
|||
this.$emit('close', () => { |
|||
this.popup.close() |
|||
}) |
|||
return |
|||
} |
|||
this.popup.close() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.uni-popup-dialog { |
|||
width: 300px; |
|||
border-radius: 15px; |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.uni-dialog-title { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
padding-top: 15px; |
|||
padding-bottom: 5px; |
|||
} |
|||
|
|||
.uni-dialog-title-text { |
|||
font-size: 16px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.uni-dialog-content { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
padding: 5px 15px 15px 15px; |
|||
} |
|||
|
|||
.uni-dialog-content-text { |
|||
font-size: 14px; |
|||
color: #6e6e6e; |
|||
} |
|||
|
|||
.uni-dialog-button-group { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
border-top-color: #f5f5f5; |
|||
border-top-style: solid; |
|||
border-top-width: 1px; |
|||
} |
|||
|
|||
.uni-dialog-button { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
|
|||
flex: 1; |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 45px; |
|||
} |
|||
|
|||
.uni-border-left { |
|||
border-left-color: #f0f0f0; |
|||
border-left-style: solid; |
|||
border-left-width: 1px; |
|||
} |
|||
|
|||
.uni-dialog-button-text { |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.uni-button-color { |
|||
color: $uni-color-primary; |
|||
} |
|||
|
|||
.uni-dialog-input { |
|||
flex: 1; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.uni-popup__success { |
|||
color: $uni-color-success; |
|||
} |
|||
|
|||
.uni-popup__warn { |
|||
color: $uni-color-warning; |
|||
} |
|||
|
|||
.uni-popup__error { |
|||
color: $uni-color-error; |
|||
} |
|||
|
|||
.uni-popup__info { |
|||
color: #909399; |
|||
} |
|||
</style> |
@ -0,0 +1,22 @@ |
|||
export default { |
|||
created() { |
|||
if (this.type === 'message') { |
|||
// 不显示遮罩
|
|||
this.maskShow = false |
|||
// 获取子组件对象
|
|||
this.childrenMsg = null |
|||
} |
|||
}, |
|||
methods: { |
|||
customOpen() { |
|||
if (this.childrenMsg) { |
|||
this.childrenMsg.open() |
|||
} |
|||
}, |
|||
customClose() { |
|||
if (this.childrenMsg) { |
|||
this.childrenMsg.close() |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,25 @@ |
|||
import message from './message.js'; |
|||
// 定义 type 类型:弹出类型:top/bottom/center
|
|||
const config = { |
|||
// 顶部弹出
|
|||
top: 'top', |
|||
// 底部弹出
|
|||
bottom: 'bottom', |
|||
// 居中弹出
|
|||
center: 'center', |
|||
// 消息提示
|
|||
message: 'top', |
|||
// 对话框
|
|||
dialog: 'center', |
|||
// 分享
|
|||
share: 'bottom', |
|||
} |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
config: config |
|||
} |
|||
}, |
|||
mixins: [message], |
|||
} |
@ -0,0 +1,294 @@ |
|||
<template> |
|||
<view v-if="showPopup" class="uni-popup" :class="[popupstyle]" @touchmove.stop.prevent="clear"> |
|||
<uni-transition v-if="maskShow" :mode-class="['fade']" :styles="maskClass" :duration="duration" |
|||
:show="showTrans" @click="onTap" /> |
|||
<uni-transition :mode-class="ani" :styles="transClass" :duration="duration" :show="showTrans" @click="onTap"> |
|||
<view class="uni-popup__wrapper-box" @click.stop="clear"> |
|||
<slot /> |
|||
</view> |
|||
</uni-transition> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import uniTransition from '../uni-transition/uni-transition.vue' |
|||
import popup from './popup.js' |
|||
/** |
|||
* PopUp 弹出层 |
|||
* @description 弹出层组件,为了解决遮罩弹层的问题 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329 |
|||
* @property {String} type = [top|center|bottom] 弹出方式 |
|||
* @value top 顶部弹出 |
|||
* @value center 中间弹出 |
|||
* @value bottom 底部弹出 |
|||
* @value message 消息提示 |
|||
* @value dialog 对话框 |
|||
* @value share 底部分享示例 |
|||
* @property {Boolean} animation = [ture|false] 是否开启动画 |
|||
* @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗 |
|||
* @event {Function} change 打开关闭弹窗触发,e={show: false} |
|||
*/ |
|||
|
|||
export default { |
|||
name: 'UniPopup', |
|||
components: { |
|||
uniTransition |
|||
}, |
|||
props: { |
|||
// 开启动画 |
|||
animation: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层 |
|||
// message: 消息提示 ; dialog : 对话框 |
|||
type: { |
|||
type: String, |
|||
default: 'center' |
|||
}, |
|||
// maskClick |
|||
maskClick: { |
|||
type: Boolean, |
|||
default: true |
|||
} |
|||
}, |
|||
provide() { |
|||
return { |
|||
popup: this |
|||
} |
|||
}, |
|||
mixins: [popup], |
|||
watch: { |
|||
/** |
|||
* 监听type类型 |
|||
*/ |
|||
type: { |
|||
handler: function(newVal) { |
|||
this[this.config[newVal]]() |
|||
}, |
|||
immediate: true |
|||
}, |
|||
/** |
|||
* 监听遮罩是否可点击 |
|||
* @param {Object} val |
|||
*/ |
|||
maskClick(val) { |
|||
this.mkclick = val |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
duration: 300, |
|||
ani: [], |
|||
showPopup: false, |
|||
showTrans: false, |
|||
maskClass: { |
|||
'position': 'fixed', |
|||
'bottom': 0, |
|||
'top': 0, |
|||
'left': 0, |
|||
'right': 0, |
|||
'backgroundColor': 'rgba(0, 0, 0, 0.4)' |
|||
}, |
|||
transClass: { |
|||
'position': 'fixed', |
|||
'left': 0, |
|||
'right': 0, |
|||
}, |
|||
maskShow: true, |
|||
mkclick: true, |
|||
popupstyle: 'top' |
|||
} |
|||
}, |
|||
created() { |
|||
this.mkclick = this.maskClick |
|||
if (this.animation) { |
|||
this.duration = 300 |
|||
} else { |
|||
this.duration = 0 |
|||
} |
|||
}, |
|||
methods: { |
|||
clear(e) { |
|||
// TODO nvue 取消冒泡 |
|||
e.stopPropagation() |
|||
}, |
|||
open() { |
|||
this.showPopup = true |
|||
this.$nextTick(() => { |
|||
new Promise(resolve => { |
|||
clearTimeout(this.timer) |
|||
this.timer = setTimeout(() => { |
|||
this.showTrans = true |
|||
// fixed by mehaotian 兼容 app 端 |
|||
this.$nextTick(() => { |
|||
resolve(); |
|||
}) |
|||
}, 50); |
|||
}).then(res => { |
|||
// 自定义打开事件 |
|||
clearTimeout(this.msgtimer) |
|||
this.msgtimer = setTimeout(() => { |
|||
this.customOpen && this.customOpen() |
|||
}, 100) |
|||
this.$emit('change', { |
|||
show: true, |
|||
type: this.type |
|||
}) |
|||
}) |
|||
}) |
|||
}, |
|||
close(type) { |
|||
this.showTrans = false |
|||
this.$nextTick(() => { |
|||
this.$emit('change', { |
|||
show: false, |
|||
type: this.type |
|||
}) |
|||
clearTimeout(this.timer) |
|||
// 自定义关闭事件 |
|||
this.customOpen && this.customClose() |
|||
this.timer = setTimeout(() => { |
|||
this.showPopup = false |
|||
}, 300) |
|||
}) |
|||
}, |
|||
onTap() { |
|||
if (!this.mkclick) return |
|||
this.close() |
|||
}, |
|||
/** |
|||
* 顶部弹出样式处理 |
|||
*/ |
|||
top() { |
|||
this.popupstyle = 'top' |
|||
this.ani = ['slide-top'] |
|||
this.transClass = { |
|||
'position': 'fixed', |
|||
'left': 0, |
|||
'right': 0, |
|||
} |
|||
}, |
|||
/** |
|||
* 底部弹出样式处理 |
|||
*/ |
|||
bottom() { |
|||
this.popupstyle = 'bottom' |
|||
this.ani = ['slide-bottom'] |
|||
this.transClass = { |
|||
'position': 'fixed', |
|||
'left': 0, |
|||
'right': 0, |
|||
'bottom': 0 |
|||
} |
|||
}, |
|||
/** |
|||
* 中间弹出样式处理 |
|||
*/ |
|||
center() { |
|||
this.popupstyle = 'center' |
|||
this.ani = ['zoom-out', 'fade'] |
|||
this.transClass = { |
|||
'position': 'fixed', |
|||
/* #ifndef APP-NVUE */ |
|||
'display': 'flex', |
|||
'flexDirection': 'column', |
|||
/* #endif */ |
|||
'bottom': 0, |
|||
'left': 0, |
|||
'right': 0, |
|||
'top': 0, |
|||
'justifyContent': 'center', |
|||
'alignItems': 'center' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style lang="scss" scoped> |
|||
.uni-popup { |
|||
position: fixed; |
|||
/* #ifndef APP-NVUE */ |
|||
z-index: 9999; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-popup__mask { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
background-color: $uni-bg-color-mask; |
|||
opacity: 0; |
|||
} |
|||
|
|||
.mask-ani { |
|||
transition-property: opacity; |
|||
transition-duration: 0.2s; |
|||
} |
|||
|
|||
.uni-top-mask { |
|||
opacity: 1; |
|||
} |
|||
|
|||
.uni-bottom-mask { |
|||
opacity: 1; |
|||
} |
|||
|
|||
.uni-center-mask { |
|||
opacity: 1; |
|||
} |
|||
|
|||
.uni-popup__wrapper { |
|||
/* #ifndef APP-NVUE */ |
|||
display: block; |
|||
/* #endif */ |
|||
position: absolute; |
|||
} |
|||
|
|||
.top { |
|||
/* #ifdef H5 */ |
|||
top: var(--window-top); |
|||
/* #endif */ |
|||
/* #ifndef H5 */ |
|||
top: 0; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.bottom { |
|||
bottom: 0; |
|||
} |
|||
|
|||
.uni-popup__wrapper-box { |
|||
/* #ifndef APP-NVUE */ |
|||
display: block; |
|||
/* #endif */ |
|||
position: relative; |
|||
/* iphonex 等安全区设置,底部安全区适配 */ |
|||
/* #ifndef APP-NVUE */ |
|||
padding-bottom: constant(safe-area-inset-bottom); |
|||
padding-bottom: env(safe-area-inset-bottom); |
|||
/* #endif */ |
|||
} |
|||
|
|||
.content-ani { |
|||
// transition: transform 0.3s; |
|||
transition-property: transform, opacity; |
|||
transition-duration: 0.2s; |
|||
} |
|||
|
|||
|
|||
.uni-top-content { |
|||
transform: translateY(0); |
|||
} |
|||
|
|||
.uni-bottom-content { |
|||
transform: translateY(0); |
|||
} |
|||
|
|||
.uni-center-content { |
|||
transform: scale(1); |
|||
opacity: 1; |
|||
} |
|||
</style> |
@ -0,0 +1,280 @@ |
|||
<template> |
|||
<view v-if="isShow" ref="ani" class="uni-transition" :class="[ani.in]" |
|||
:style="'transform:' +transform+';'+stylesObject" @click="change"> |
|||
<slot></slot> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// #ifdef APP-NVUE |
|||
const animation = uni.requireNativePlugin('animation'); |
|||
// #endif |
|||
/** |
|||
* Transition 过渡动画 |
|||
* @description 简单过渡动画组件 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id=985 |
|||
* @property {Boolean} show = [false|true] 控制组件显示或隐藏 |
|||
* @property {Array} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型 |
|||
* @value fade 渐隐渐出过渡 |
|||
* @value slide-top 由上至下过渡 |
|||
* @value slide-right 由右至左过渡 |
|||
* @value slide-bottom 由下至上过渡 |
|||
* @value slide-left 由左至右过渡 |
|||
* @value zoom-in 由小到大过渡 |
|||
* @value zoom-out 由大到小过渡 |
|||
* @property {Number} duration 过渡动画持续时间 |
|||
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` |
|||
*/ |
|||
export default { |
|||
name: 'uniTransition', |
|||
props: { |
|||
show: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
modeClass: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
duration: { |
|||
type: Number, |
|||
default: 300 |
|||
}, |
|||
styles: { |
|||
type: Object, |
|||
default () { |
|||
return {} |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
isShow: false, |
|||
transform: '', |
|||
ani: { |
|||
in: '', |
|||
active: '' |
|||
} |
|||
}; |
|||
}, |
|||
watch: { |
|||
show: { |
|||
handler(newVal) { |
|||
if (newVal) { |
|||
this.open() |
|||
} else { |
|||
this.close() |
|||
} |
|||
}, |
|||
immediate: true |
|||
} |
|||
}, |
|||
computed: { |
|||
stylesObject() { |
|||
let styles = { |
|||
...this.styles, |
|||
'transition-duration': this.duration / 1000 + 's' |
|||
} |
|||
let transfrom = '' |
|||
for (let i in styles) { |
|||
let line = this.toLine(i) |
|||
transfrom += line + ':' + styles[i] + ';' |
|||
} |
|||
return transfrom |
|||
} |
|||
}, |
|||
created() { |
|||
// this.timer = null |
|||
// this.nextTick = (time = 50) => new Promise(resolve => { |
|||
// clearTimeout(this.timer) |
|||
// this.timer = setTimeout(resolve, time) |
|||
// return this.timer |
|||
// }); |
|||
}, |
|||
methods: { |
|||
change() { |
|||
this.$emit('click', { |
|||
detail: this.isShow |
|||
}) |
|||
}, |
|||
open() { |
|||
clearTimeout(this.timer) |
|||
this.isShow = true |
|||
this.transform = '' |
|||
this.ani.in = '' |
|||
for (let i in this.getTranfrom(false)) { |
|||
if (i === 'opacity') { |
|||
this.ani.in = 'fade-in' |
|||
} else { |
|||
this.transform += `${this.getTranfrom(false)[i]} ` |
|||
} |
|||
} |
|||
this.$nextTick(() => { |
|||
setTimeout(() => { |
|||
this._animation(true) |
|||
}, 50) |
|||
}) |
|||
|
|||
}, |
|||
close(type) { |
|||
clearTimeout(this.timer) |
|||
this._animation(false) |
|||
}, |
|||
_animation(type) { |
|||
let styles = this.getTranfrom(type) |
|||
// #ifdef APP-NVUE |
|||
if (!this.$refs['ani']) return |
|||
animation.transition(this.$refs['ani'].ref, { |
|||
styles, |
|||
duration: this.duration, //ms |
|||
timingFunction: 'ease', |
|||
needLayout: false, |
|||
delay: 0 //ms |
|||
}, () => { |
|||
if (!type) { |
|||
this.isShow = false |
|||
} |
|||
this.$emit('change', { |
|||
detail: this.isShow |
|||
}) |
|||
}) |
|||
// #endif |
|||
// #ifndef APP-NVUE |
|||
this.transform = '' |
|||
for (let i in styles) { |
|||
if (i === 'opacity') { |
|||
this.ani.in = `fade-${type?'out':'in'}` |
|||
} else { |
|||
this.transform += `${styles[i]} ` |
|||
} |
|||
} |
|||
this.timer = setTimeout(() => { |
|||
if (!type) { |
|||
this.isShow = false |
|||
} |
|||
this.$emit('change', { |
|||
detail: this.isShow |
|||
}) |
|||
|
|||
}, this.duration) |
|||
// #endif |
|||
|
|||
}, |
|||
getTranfrom(type) { |
|||
let styles = { |
|||
transform: '' |
|||
} |
|||
this.modeClass.forEach((mode) => { |
|||
switch (mode) { |
|||
case 'fade': |
|||
styles.opacity = type ? 1 : 0 |
|||
break; |
|||
case 'slide-top': |
|||
styles.transform += `translateY(${type?'0':'-100%'}) ` |
|||
break; |
|||
case 'slide-right': |
|||
styles.transform += `translateX(${type?'0':'100%'}) ` |
|||
break; |
|||
case 'slide-bottom': |
|||
styles.transform += `translateY(${type?'0':'100%'}) ` |
|||
break; |
|||
case 'slide-left': |
|||
styles.transform += `translateX(${type?'0':'-100%'}) ` |
|||
break; |
|||
case 'zoom-in': |
|||
styles.transform += `scale(${type?1:0.8}) ` |
|||
break; |
|||
case 'zoom-out': |
|||
styles.transform += `scale(${type?1:1.2}) ` |
|||
break; |
|||
} |
|||
}) |
|||
return styles |
|||
}, |
|||
_modeClassArr(type) { |
|||
let mode = this.modeClass |
|||
if (typeof(mode) !== "string") { |
|||
let modestr = '' |
|||
mode.forEach((item) => { |
|||
modestr += (item + '-' + type + ',') |
|||
}) |
|||
return modestr.substr(0, modestr.length - 1) |
|||
} else { |
|||
return mode + '-' + type |
|||
} |
|||
}, |
|||
// getEl(el) { |
|||
// console.log(el || el.ref || null); |
|||
// return el || el.ref || null |
|||
// }, |
|||
toLine(name) { |
|||
return name.replace(/([A-Z])/g, "-$1").toLowerCase(); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.uni-transition { |
|||
transition-timing-function: ease; |
|||
transition-duration: 0.3s; |
|||
transition-property: transform, opacity; |
|||
} |
|||
|
|||
.fade-in { |
|||
opacity: 0; |
|||
} |
|||
|
|||
.fade-active { |
|||
opacity: 1; |
|||
} |
|||
|
|||
.slide-top-in { |
|||
/* transition-property: transform, opacity; */ |
|||
transform: translateY(-100%); |
|||
} |
|||
|
|||
.slide-top-active { |
|||
transform: translateY(0); |
|||
/* opacity: 1; */ |
|||
} |
|||
|
|||
.slide-right-in { |
|||
transform: translateX(100%); |
|||
} |
|||
|
|||
.slide-right-active { |
|||
transform: translateX(0); |
|||
} |
|||
|
|||
.slide-bottom-in { |
|||
transform: translateY(100%); |
|||
} |
|||
|
|||
.slide-bottom-active { |
|||
transform: translateY(0); |
|||
} |
|||
|
|||
.slide-left-in { |
|||
transform: translateX(-100%); |
|||
} |
|||
|
|||
.slide-left-active { |
|||
transform: translateX(0); |
|||
opacity: 1; |
|||
} |
|||
|
|||
.zoom-in-in { |
|||
transform: scale(0.8); |
|||
} |
|||
|
|||
.zoom-out-active { |
|||
transform: scale(1); |
|||
} |
|||
|
|||
.zoom-out-in { |
|||
transform: scale(1.2); |
|||
} |
|||
</style> |
@ -0,0 +1,22 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<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="/main.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,99 @@ |
|||
import App from './App' |
|||
import toast from './common/Toast.js' |
|||
import wxAuthLogin from './common/wxAuthLogin.js' |
|||
import wxSilentLogin from './common/wxSilentLogin.js' |
|||
import { |
|||
isEmpty |
|||
} from './common/TextUtils.js' |
|||
import { |
|||
write, |
|||
read, |
|||
write2, |
|||
read2 |
|||
} from './common/PreferenceHelper.js' |
|||
import { |
|||
writeGameCahce, |
|||
clearGameCache, |
|||
readGameCahce, |
|||
hasCache |
|||
} from './common/GameCache.js' |
|||
import { |
|||
http, |
|||
httpOtherUrl, |
|||
httpCookie, |
|||
baseImgURL, |
|||
upload |
|||
} from './common/Http.js' |
|||
import px2upx from './common/PxToRpxOrPxToUpx.js' |
|||
import { |
|||
timeText, |
|||
timeFormat, |
|||
currentMillions, |
|||
getWeekStr, |
|||
getTodayEndTime |
|||
} from "./common/Time.js" |
|||
import { |
|||
back, |
|||
setResult, |
|||
onActivityResult |
|||
} from "./common/Back.js" |
|||
import getDistance from 'common/Distance.js' |
|||
|
|||
import chooseUpload from 'common/ChooseUpload.js' |
|||
import { |
|||
putWEBExtra, |
|||
getWEBExtra |
|||
} from './common/intent.js' |
|||
|
|||
Vue.prototype.WxSilentLogin = wxSilentLogin |
|||
Vue.prototype.WxAuthLogin = wxAuthLogin |
|||
Vue.prototype.Toast = toast |
|||
Vue.prototype.Back = back |
|||
Vue.prototype.WritePreference = write |
|||
Vue.prototype.WritePreference2 = write2 |
|||
Vue.prototype.ReadPreference = read |
|||
Vue.prototype.ReadPreference2 = read2 |
|||
Vue.prototype.HTTP = http |
|||
Vue.prototype.HttpOtherUrl = httpOtherUrl |
|||
Vue.prototype.Upload = upload |
|||
Vue.prototype.HttpCookie = httpCookie |
|||
Vue.prototype.IsEmpty = isEmpty |
|||
Vue.prototype.Px2Upx = px2upx |
|||
Vue.prototype.TimeText = timeText |
|||
Vue.prototype.TimeFormat = timeFormat |
|||
Vue.prototype.GetWeekStr = getWeekStr |
|||
Vue.prototype.CurrentMillions = currentMillions |
|||
Vue.prototype.GetTodayEndTime = getTodayEndTime |
|||
Vue.prototype.Back = back |
|||
Vue.prototype.SetResult = setResult |
|||
Vue.prototype.OnActivityResult = onActivityResult |
|||
Vue.prototype.WriteGameCahce = writeGameCahce |
|||
Vue.prototype.ClearGameCache = clearGameCache |
|||
Vue.prototype.ReadGameCahce = readGameCahce |
|||
Vue.prototype.HasCache = hasCache |
|||
Vue.prototype.getDistance = getDistance |
|||
Vue.prototype.chooseUpload = chooseUpload |
|||
Vue.prototype.putWEBExtra = putWEBExtra |
|||
Vue.prototype.getWEBExtra = getWEBExtra |
|||
|
|||
// #ifndef VUE3
|
|||
import Vue from 'vue' |
|||
Vue.config.productionTip = false |
|||
App.mpType = 'app' |
|||
const app = new Vue({ |
|||
...App |
|||
}) |
|||
app.$mount() |
|||
// #endif
|
|||
|
|||
// #ifdef VUE3
|
|||
import { |
|||
createSSRApp |
|||
} from 'vue' |
|||
export function createApp() { |
|||
const app = createSSRApp(App) |
|||
return { |
|||
app |
|||
} |
|||
} |
|||
// #endif
|
@ -0,0 +1,68 @@ |
|||
{ |
|||
"name" : "signUpTool", |
|||
"appid" : "__UNI__8EDFE3C", |
|||
"description" : "", |
|||
"versionName" : "1.0.0", |
|||
"versionCode" : "100", |
|||
"transformPx" : false, |
|||
/* 5+App特有相关 */ |
|||
"app-plus" : { |
|||
"usingComponents" : true, |
|||
"nvueStyleCompiler" : "uni-app", |
|||
"compilerVersion" : 3, |
|||
"splashscreen" : { |
|||
"alwaysShowBeforeRender" : true, |
|||
"waiting" : true, |
|||
"autoclose" : true, |
|||
"delay" : 0 |
|||
}, |
|||
/* 模块配置 */ |
|||
"modules" : {}, |
|||
/* 应用发布信息 */ |
|||
"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\"/>" |
|||
] |
|||
}, |
|||
/* ios打包配置 */ |
|||
"ios" : {}, |
|||
/* SDK配置 */ |
|||
"sdkConfigs" : {} |
|||
} |
|||
}, |
|||
/* 快应用特有相关 */ |
|||
"quickapp" : {}, |
|||
/* 小程序特有相关 */ |
|||
"mp-weixin" : { |
|||
"appid" : "wxf869f371c1d0610c", |
|||
"setting" : { |
|||
"urlCheck" : true, |
|||
"postcss" : true, |
|||
"minified" : false, |
|||
"es6" : true |
|||
}, |
|||
"usingComponents" : true, |
|||
"permission" : { |
|||
"scope.userLocation" : { |
|||
"desc" : "获取当前所在城市" |
|||
} |
|||
}, |
|||
"lazyCodeLoading" : "requiredComponents" |
|||
} |
|||
} |
@ -0,0 +1,255 @@ |
|||
{ |
|||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages |
|||
{ |
|||
"path": "pages/publish/Publish", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/publish/EnrollCondition", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/publish/EnrollRequired", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/find/ActivityList", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/find/ActivityDetail", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/find/BaoMingListActivity", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/find/TeamEnroll", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/team/MyTeam", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/test/DetailActivity", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/team/recruitList", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/test/test", |
|||
"style": {} |
|||
}, |
|||
|
|||
{ |
|||
"path": "pages/team/CreateTeam", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/team/MyTeam2", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/team/JoinTeam", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/index/firstActivity", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/index/sponsorDetail", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/index/activity", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/index/addSponsor", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/index/activityList", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/index/DetailActivity", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/index/ArenaDetailActivity", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/index/BindPhone", |
|||
"style": { |
|||
|
|||
} |
|||
|
|||
}, { |
|||
"path": "pages/index/UserAuthentication", |
|||
"style": { |
|||
|
|||
} |
|||
|
|||
}, { |
|||
"path": "pages/index/sponsorList", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/index/mobileInfoActivity", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/index/eventActivity", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/index/explainActivity", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/index/InputHappyGameLimitActivity", |
|||
"style": {} |
|||
|
|||
}, { |
|||
"path": "pages/index/InputHappyGameAreaActivity", |
|||
"style": {} |
|||
|
|||
}, { |
|||
"path": "pages/city/CitySelectActivity", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/web/WebActivity", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/setup/upMobile", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/setup/upMobile2", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/setup/RetrievePasswordActivity", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/setup/NewPasswordActivity", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/me/PersonalCenter", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/me/AuthLogin", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/me/BaseInfo", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/me/MyActivity", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/me/ModiNickName", |
|||
"style": {} |
|||
},{ |
|||
"path": "pages/me/ModiUserNumber", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/me/MyTeamCreate", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/me/MemberManage", |
|||
"style": {} |
|||
} |
|||
, { |
|||
"path": "pages/me/MyTeam", |
|||
"style": {} |
|||
}, { |
|||
"path": "pages/me/RealInfo", |
|||
"style": {} |
|||
},{ |
|||
"path": "pages/me/Setup", |
|||
"style": {} |
|||
},{ |
|||
"path": "pages/me/ModiRealName", |
|||
"style": {} |
|||
},{ |
|||
"path": "pages/me/ModiMobileInputNew", |
|||
"style": {} |
|||
},{ |
|||
"path": "pages/me/ModiPasswordVerify", |
|||
"style": {} |
|||
},{ |
|||
"path": "pages/me/ModiPassword", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/publish/CreateActivity", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/publish/EditIntroduction", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/publish/EditDisclaimer", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/publish/SetLinker", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/publish/InputEnrollNumbersLimitMoney", |
|||
"style": {} |
|||
}, |
|||
{ |
|||
"path": "pages/team/WaitJoin", |
|||
"style": {} |
|||
} |
|||
|
|||
|
|||
|
|||
], |
|||
// 下载安装时配置 |
|||
"easycom": { |
|||
"autoscan": true, |
|||
"custom": { |
|||
"fui-(.*)": "@/components/firstui/fui-$1/fui-$1.vue" |
|||
} |
|||
}, |
|||
"globalStyle": { |
|||
"navigationStyle": "custom", |
|||
"navigationBarBackgroundColor": "#2fa1f0" |
|||
}, |
|||
|
|||
"tabBar": { |
|||
"color": "#959595", |
|||
"selectedColor": "#41adf8", |
|||
"backgroundColor": "#FFFFFF", |
|||
"borderStyle": "black", |
|||
"list": [{ |
|||
"pagePath": "pages/find/ActivityList", |
|||
"iconPath": "static/img/public/bottom-icon/find_normal.png", |
|||
"selectedIconPath": "static/img/public/bottom-icon/find_press.png", |
|||
"text": "发现" |
|||
}, { |
|||
"pagePath": "pages/publish/Publish", |
|||
"iconPath": "static/img/public/bottom-icon/add_normal.png", |
|||
"selectedIconPath": "static/img/public/bottom-icon/add_press.png", |
|||
"text": "发布" |
|||
}, { |
|||
"pagePath": "pages/me/PersonalCenter", |
|||
"iconPath": "static/img/public/bottom-icon/mine_normal.png", |
|||
"selectedIconPath": "static/img/public/bottom-icon/mine_press.png", |
|||
"text": "我的" |
|||
}] |
|||
|
|||
}, |
|||
"condition" : { //模式配置,仅开发期间生效 |
|||
"current": 0, //当前激活的模式(list 的索引项) |
|||
"list": [ |
|||
{ |
|||
"name": "", //模式名称 |
|||
"path": "", //启动页面,必选 |
|||
"query": "" //启动参数,在页面的onLoad函数里面得到 |
|||
} |
|||
] |
|||
} |
|||
} |
@ -0,0 +1,164 @@ |
|||
<template> |
|||
<RefreshView ref="mescrollRef" :hasBack="true" @backClick="backClick" :isInterceptBack="true" text="选择城市" :useDownScroll="false" |
|||
:useUpScroll="false" pageBg="#F1F2F5"> |
|||
<view style="padding-bottom: 200rpx;"> |
|||
<city-select @cityClick="cityClick" :formatName="formatName" :activeCity="activeCity" :hotCity="hotCity" |
|||
:obtainCitys="obtainCitys" :isSearch="true" ref="citys"></city-select> |
|||
</view> |
|||
</RefreshView> |
|||
</template> |
|||
|
|||
<script> |
|||
import citys from '@/components/city-select/citys.js' |
|||
import citySelect from '@/components/city-select/city-select.vue' |
|||
export default { |
|||
components: { |
|||
citySelect |
|||
}, |
|||
data() { |
|||
return { |
|||
//需要构建索引参数的名称(注意:传递的对象里面必须要有这个名称的参数) |
|||
formatName: 'title', |
|||
//当前城市 |
|||
activeCity: { |
|||
id: 1, |
|||
title: '南京市' |
|||
}, |
|||
//热门城市 |
|||
hotCity: [{ |
|||
id: 0, |
|||
title: '南京市' |
|||
}, |
|||
{ |
|||
id: 1, |
|||
title: '南京市' |
|||
} |
|||
], |
|||
//显示的城市数据 |
|||
obtainCitys: [{ |
|||
id: 0, |
|||
title: '南京' |
|||
}, |
|||
{ |
|||
id: 1, |
|||
title: '北京' |
|||
}, |
|||
{ |
|||
id: 2, |
|||
title: '天津' |
|||
}, |
|||
{ |
|||
id: 3, |
|||
title: '东京' |
|||
} |
|||
], |
|||
location: { |
|||
city: "", |
|||
code: "" |
|||
} |
|||
} |
|||
}, |
|||
onLoad: function(option) { |
|||
console.log(option); |
|||
this.location.city = option.city |
|||
this.location.code = option.code |
|||
|
|||
//修改需要构建索引参数的名称 |
|||
this.formatName = 'cityName' |
|||
//修改当前城市 |
|||
this.activeCity = { |
|||
cityName: '正在定位', |
|||
cityCode: 0 |
|||
} |
|||
//修改热门城市 |
|||
this.hotCity = [{ |
|||
cityName: '北京', |
|||
cityCode: 110000 |
|||
}, |
|||
{ |
|||
cityName: '上海', |
|||
cityCode: 310000 |
|||
}, { |
|||
cityName: '广州', |
|||
cityCode: 440100 |
|||
}, { |
|||
cityName: '深圳', |
|||
cityCode: 440300 |
|||
}, { |
|||
cityName: '杭州', |
|||
cityCode: 330100 |
|||
}, |
|||
] |
|||
//修改构建索引数据 |
|||
this.obtainCitys = citys |
|||
let _this = this |
|||
uni.getLocation({ |
|||
type: 'gcj02', |
|||
success: function(res) { |
|||
|
|||
let url = |
|||
"https://restapi.amap.com/v3/geocode/regeo?key=59970402d1c3f7dc1efff17d4dfcff21&location=" + |
|||
res.longitude + "," + res.latitude + |
|||
"&poitype=&radius=1000&extensions=all&batch=false&roadlevel=0"; |
|||
|
|||
_this.HttpOtherUrl({ |
|||
url: url |
|||
}).then((res) => { |
|||
|
|||
let json = JSON.stringify(res); |
|||
let info = JSON.parse(json).regeocode.addressComponent; |
|||
// 城市 |
|||
let city = info.city; |
|||
// 城市编码 |
|||
let code = info.adcode; |
|||
|
|||
//修改需要构建索引参数的名称 |
|||
_this.formatName = 'cityName' |
|||
//修改当前城市 |
|||
_this.activeCity = { |
|||
cityName: city, |
|||
cityCode: code |
|||
} |
|||
}) |
|||
}, |
|||
fail() { |
|||
//修改当前城市 |
|||
_this.activeCity = { |
|||
cityName: '定位失败', |
|||
cityCode: 0 |
|||
} |
|||
} |
|||
}); |
|||
}, |
|||
methods: { |
|||
backClick(){ |
|||
console.log("location:", this.location); |
|||
this.SetResult(this.location) |
|||
}, |
|||
cityClick(item) { |
|||
let json = JSON.stringify(item); |
|||
|
|||
if (JSON.parse(json).cityCode == 0) { |
|||
this.Toast("当前选择无效") |
|||
} else { |
|||
console.log("city:", JSON.parse(json).cityName); |
|||
console.log("code:", JSON.parse(json).cityCode); |
|||
|
|||
this.location.city = JSON.parse(json).cityName, |
|||
this.location.code = JSON.parse(json).cityCode |
|||
|
|||
this.SetResult(this.location) |
|||
// uni.$emit('location', { |
|||
// city: JSON.parse(json).cityName, |
|||
// code: JSON.parse(json).cityCode |
|||
// }); |
|||
// this.Back() |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
@ -0,0 +1,482 @@ |
|||
<template> |
|||
<RefreshView ref="mescrollRef" text="活动详情" :useDownScroll="false" :useUpScroll="false" |
|||
:useTitleLeftBtn="parameter.userRoleId==1?1:0" titleLeftBtnSource=" 管理" :dropLeftList="page.manageList" @drop="selectManage"> |
|||
<view class="activity-detail"> |
|||
<!-- 广告 --> |
|||
<view class="image-area"> |
|||
<swiper indicator-dots="true" autoplay="true"> |
|||
<swiper-item v-for="(item,index) in page.activityDetails.listCoverImageUrl" :key="index"> |
|||
<image class="image" :src="item" mode="aspectFill"></image> |
|||
</swiper-item> |
|||
</swiper> |
|||
</view> |
|||
<!-- 名称和活动类型 --> |
|||
<view class="name-category"> |
|||
<h1>{{page.activityDetails.name}}</h1> |
|||
<text class="category">{{page.activityDetails.sportCategoryName}}</text> |
|||
</view> |
|||
|
|||
<!-- 报名截止和整体活动时间 --> |
|||
<view class="row"> |
|||
<!-- <view style="display: flex;flex-direction: row;margin-top: 15rpx;align-items: center;margin-left: 20rpx;margin-right: 30rpx;"> --> |
|||
<text class="field">报名截止:</text> |
|||
<text>{{page.activityDetails.enrollEndTime}}</text> |
|||
<text style="color: #E99D42;font-size: 26rpx;flex: 1;text-align: right;">还有1天</text> |
|||
</view> |
|||
|
|||
<view class="line-thin"></view> |
|||
<view class="row"> |
|||
<text class="field">整体活动:</text> |
|||
<view style="display: flex;flex-direction: column;"> |
|||
<text>{{page.activityDetails.startTime}}</text> |
|||
<text class="margin-top20">{{page.activityDetails.endTime}}</text> |
|||
</view> |
|||
</view> |
|||
<!-- 活动项目 --> |
|||
<view v-for="(item,index) in page.activityDetails.listActivityItem" :key="index" > |
|||
<view class="item-area"> |
|||
<!-- 活动项目名称类型金额 --> |
|||
<view class="item-name-mold-money"> |
|||
<h2>{{item.name}}</h2> |
|||
<text class="mold-money">{{item.enrollMoldName}}/{{item.enrollMoney==0?"免费":item.enrollMoney+'元'}}</text> |
|||
</view> |
|||
<!-- 活动项目介绍 --> |
|||
<view class="item-introduction"> |
|||
<text>{{item.introduction}}</text> |
|||
</view> |
|||
<view class="line-thin"></view> |
|||
<!-- 日期地点 --> |
|||
<view class="row"> |
|||
<text class="date">{{item.startTime}}</text> |
|||
<text class="address">{{item.address}}</text> |
|||
<image v-if="!showAddress(item.gymnasiumSid)" style="width: 30rpx;height: 30rpx;" src="../../static/img/public/more.png" @click="gymnasiumName(item.gymnasiumSid)"></image> |
|||
</view> |
|||
<view class="line-thin"></view> |
|||
<view class="row"> |
|||
<text class="field">报名数</text> |
|||
<text class="margin-left20 ">{{item.enrollNumbers==0?"暂无":item.enrollNumbers+'人'}}/{{item.enrollNumbersLimit==0?"不限":item.enrollNumbersLimit+'人'}}</text> |
|||
<view v-if="item.listUserHeadImageUrl.length>0" style="display:flex;flex-direction: row; margin-left:10rpx;" @click="userList(item.sid)"> |
|||
<view v-for="(url,i) in item.listUserHeadImageUrl " :key="i"> |
|||
<view |
|||
style="display:flex; width:100%;margin-left:2rpx;margin-right:2rpx;"> |
|||
<image style="border-radius: 50%; width: 50rpx;height: 50rpx;" :src="url" mode="aspectFill"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<image class="more" src="../../static/img/public/more.png" ></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="line-thin"></view> |
|||
<!-- 活动介绍 --> |
|||
<view class="row"> |
|||
<text class="field">活动介绍</text> |
|||
<view class="line"></view> |
|||
</view> |
|||
<text class="introduction">{{page.activityDetails.introduction}}</text> |
|||
<!-- 奖品奖项 --> |
|||
<view class="row"> |
|||
<text class="field">奖品奖项</text> |
|||
<view class="line"></view> |
|||
</view> |
|||
<text class="introduction">{{page.activityDetails.notes}}</text> |
|||
<!-- 特别鸣谢 --> |
|||
<view class="row"> |
|||
<text class="field">特别鸣谢</text> |
|||
<view class="line"></view> |
|||
</view> |
|||
<view |
|||
style="display: flex;margin-left: 40rpx;margin-right: 40rpx;flex-direction: column; margin-top: 30rpx;"> |
|||
<view v-for="(item,pos) in page.activityDetails.listSpecialThanks " :key="pos"> |
|||
<view |
|||
style="display: flex; width: 100%; padding-top: 10rpx;padding-bottom: 10rpx;justify-content: center;"> |
|||
<text style="color: #898989;font: size 28rpx;flex: 1;">{{item.name}}</text> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
<view class="line-thin"></view> |
|||
<!-- 主办方 --> |
|||
<view class="row"> |
|||
<text class="field">主办方:</text> |
|||
<text style="color: #898989;font-size: 25rpx;">{{page.activityDetails.organizer}}</text> |
|||
</view> |
|||
<view class="line-thin"></view> |
|||
<!-- 联系人 --> |
|||
<view class="row"> |
|||
<text class="field">联系人:</text> |
|||
<view style="display: flex;flex-direction: row;"> |
|||
<text style="color: #898989;font-size: 25rpx;">{{page.activityDetails.linkerName}}</text> |
|||
<view style="width: 30rpx;"></view> |
|||
<text style="color: #898989; font-size: 25rpx;">{{page.activityDetails.linkerPhone}}</text> |
|||
</view> |
|||
|
|||
</view> |
|||
<view class="line-thin"></view> |
|||
<view class="row"> |
|||
<text class="field">报名必填</text> |
|||
<text>{{page.enrollRequiredName}}</text> |
|||
<!-- <image class = "more" src="../../static/img/public/more.png" ></image> --> |
|||
</view> |
|||
<view class="line-wide"></view> |
|||
<!-- 报名条款及按钮 --> |
|||
<view class="agreeMent"> |
|||
<checkbox-group @change="checkboxChange"> |
|||
<checkbox style="transform:scale(0.7)" :checked="checked1"></checkbox> |
|||
</checkbox-group> |
|||
<text class="text2">我已阅读并同意</text> |
|||
<text style="color: #007AFF;">《参赛须知》</text> |
|||
<text style="display: flex;text-align: center; margin-left: 20rpx; padding: 5rpx 10rpx;background: #0081D5; |
|||
color: #FFFFFF; font-size: 28rpx;" @click="enroll()"> 我要报名 </text> |
|||
</view> |
|||
<view style="height: 300rpx;"></view> |
|||
</view> |
|||
</RefreshView> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
parameter:{ |
|||
activitySid:"", |
|||
userRoleId:0, |
|||
userRoleName:"游客", |
|||
passRequired : false, |
|||
noPassMsg : "" |
|||
}, |
|||
isCreate:0, |
|||
checked1: true, |
|||
page:{ |
|||
activityDetails:{}, |
|||
manageList: [{ |
|||
'name': '修改活动', |
|||
'src': '../../static/game-icon/renyuanguanli.png', |
|||
'id': 0 |
|||
}, { |
|||
'name': '删除活动', |
|||
'src': '../../static/game-icon/shanchu.png', |
|||
'id': 1 |
|||
}], |
|||
enrollRequiredName:"" |
|||
}, |
|||
} |
|||
}, |
|||
onLoad: function(option) { |
|||
// 为参数赋值 |
|||
this.parameter.activitySid = option.activitySid |
|||
// 如果用户登陆则获取用户角色 |
|||
let _this = this |
|||
if (getApp().globalData.isLogin){ |
|||
_this.parameter.userRoleId =1 |
|||
let sysUserSid = getApp().globalData.sysUserSid; |
|||
let activitySid = _this.parameter.activitySid; |
|||
_this.HTTP({ |
|||
url: 'aos/v1/aosUser/getActivityRole', |
|||
method: 'GET', |
|||
data: { |
|||
'sysUserSid': sysUserSid, |
|||
'activitySid':activitySid |
|||
}, |
|||
paramsType: "FORM", |
|||
loading: true |
|||
}).then((res) => { |
|||
if (200 == res.code) { |
|||
_this.parameter.userRoleId = res.data.userRoleId |
|||
_this.parameter.userRoleName = res.data.userRoleName |
|||
// } else { |
|||
// _this.Toast(res.msg) |
|||
} |
|||
}); |
|||
}else{ |
|||
_this.parameter.userRoleId = 0 |
|||
} |
|||
|
|||
}, |
|||
onShow() { |
|||
let _this = this |
|||
let activitySid = _this.parameter.activitySid; |
|||
_this.HTTP({ |
|||
url: 'aos/v1/activity/getActivityDetails/' + activitySid, |
|||
method: 'GET', |
|||
paramsType: "FORM", |
|||
loading: true |
|||
}).then((res) => { |
|||
if (200 == res.code) { |
|||
_this.page.activityDetails = res.data |
|||
_this.setEnrollRequiredName() |
|||
} else { |
|||
_this.Toast(res.msg) |
|||
} |
|||
}); |
|||
}, |
|||
methods: { |
|||
setEnrollRequiredName(){ |
|||
let enrollRequiredName = "" |
|||
if (this.page.activityDetails.enrollRequired.onRealName == 1){ |
|||
enrollRequiredName += "姓名" |
|||
} |
|||
if (this.page.activityDetails.enrollRequired.onSex == 1){ |
|||
if (this.page.activityDetails.enrollRequired.onRealName == 1){ |
|||
enrollRequiredName += " / " |
|||
} |
|||
enrollRequiredName += "性别" |
|||
} |
|||
if (this.page.activityDetails.enrollRequired.onBirthday == 1){ |
|||
if (this.page.activityDetails.enrollRequired.onSex == 1 || this.page.activityDetails.enrollRequired.onRealName == 1){ |
|||
enrollRequiredName += " / " |
|||
} |
|||
enrollRequiredName += "生日" |
|||
} |
|||
if (this.page.activityDetails.enrollRequired.onAdCode == 1){ |
|||
if (this.page.activityDetails.enrollRequired.onSex == 1 || this.page.activityDetails.enrollRequired.onRealName == 1 || this.page.activityDetails.enrollRequired.onBirthday == 1){ |
|||
enrollRequiredName += " / " |
|||
} |
|||
enrollRequiredName += "地区" |
|||
} |
|||
this.page.enrollRequiredName = enrollRequiredName |
|||
}, |
|||
selectManage(index, isLeft, selectData) { |
|||
let _this = this; |
|||
let id = selectData.id |
|||
if (id == 0) { |
|||
// 修改活动 |
|||
uni.navigateTo({ |
|||
url: '../publish/CreateActivity?sid=' + _this.parameter.activitySid |
|||
}) |
|||
} else if (id == 1) { |
|||
// 删除活动 |
|||
uni.showModal({ |
|||
title: '温馨提示', |
|||
content: '确定删除此赛事?', |
|||
success(res) { |
|||
if (res.confirm) { |
|||
_this.HTTP({ |
|||
url: 'aos/v1/activity/delActivity/' + _this.parameter.activitySid, |
|||
method: 'DELETE', |
|||
paramsType: "FORM", |
|||
loading: true |
|||
}).then((res) => { |
|||
if (200 == res.code) { |
|||
uni.navigateBack({ |
|||
delta: 1 |
|||
}); |
|||
} else { |
|||
_this.Toast(res.msg) |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} else if (id == -1) { |
|||
// 关闭 |
|||
} |
|||
}, |
|||
toCreateActivity(){ |
|||
let activitySid = this.parameter.activitySid |
|||
uni.navigateTo({ |
|||
url: '../publish/CreateActivity?sid=' + activitySid |
|||
}); |
|||
}, |
|||
showAddress(s) { |
|||
console.log("..." + s); |
|||
return this.IsEmpty(s) |
|||
}, |
|||
checkboxChange(e) { |
|||
this.checked1 = !this.checked1 |
|||
console.log(this.checked1) |
|||
}, |
|||
gymnasiumName(e) { |
|||
uni.navigateTo({ |
|||
url: "ArenaDetailActivity?sid=" + e |
|||
}) |
|||
console.log("gymnasiumName>>", e) |
|||
}, |
|||
userList(sid) { |
|||
console.log("userList>>", sid) |
|||
uni.navigateTo({ |
|||
url: "BaoMingListActivity?activitySid=" + this.parameter.activitySid + "&activityItemSid=" + sid |
|||
}) |
|||
|
|||
}, |
|||
enroll() { |
|||
let _this = this; |
|||
|
|||
// 勾选协议 |
|||
if (!this.checked1) { |
|||
this.Toast("请认真阅读参赛须知,并勾选。") |
|||
return |
|||
} |
|||
// 报名菜单赋值 |
|||
let listEnrollMenu = [] |
|||
let listActivityItem = this.page.activityDetails.listActivityItem |
|||
for (var i = 0; i < listActivityItem.length; i++) { |
|||
let enrollMenu = listActivityItem[i].name |
|||
let enrollMold = listActivityItem[i].enrollMold |
|||
if ( enrollMold = 1){ |
|||
enrollMenu += "(" + listActivityItem[i].enrollMoldName +")" |
|||
} |
|||
listEnrollMenu.push(enrollMenu) |
|||
} |
|||
|
|||
// 输出报名菜单 |
|||
uni.showActionSheet({ |
|||
itemList: listEnrollMenu, |
|||
success: function(res) { |
|||
console.log("itemList==" + listEnrollMenu) |
|||
console.log("res==" + JSON.stringify(res)) |
|||
_this.selectMenu(res.tapIndex, listEnrollMenu) |
|||
}, |
|||
fail(e) { |
|||
console.log("reeees==" + JSON.stringify(e)) |
|||
} |
|||
}); |
|||
|
|||
}, |
|||
verifyEnrollRequired(){ |
|||
|
|||
}, |
|||
selectMenu(id, info) { |
|||
// 用户如未登陆则进入授权登陆页面 |
|||
if (!getApp().globalData.isLogin){ |
|||
uni.navigateTo({ |
|||
url: "../me/AuthLogin" |
|||
}) |
|||
} |
|||
let _this = this |
|||
let activitySid = _this.page.activityDetails.sid |
|||
let sysUserSid = getApp().globalData.sysUserSid |
|||
// console.log("报名模式=============",JSON.stringify(_this.page.activityDetails)) |
|||
// 校验报名必填项 |
|||
let onRealName = _this.page.activityDetails.enrollRequired.onRealName |
|||
let onSex = _this.page.activityDetails.enrollRequired.onSex |
|||
let onBirthday = _this.page.activityDetails.enrollRequired.onBirthday |
|||
let onAdCode = _this.page.activityDetails.enrollRequired.onAdCode |
|||
if (onRealName == 0 || onSex == 0 || onBirthday == 0 || onAdCode == 0){ |
|||
_this.HTTP({ |
|||
url: 'aos/v1/activity/verifyEnrollRequired', |
|||
method: 'GET', |
|||
paramsType: "FORM", |
|||
data: { |
|||
'activitySid': activitySid, |
|||
'sysUserSid': sysUserSid |
|||
}, |
|||
loading: true |
|||
}).then((res) => { |
|||
if ( res.code == "200") { |
|||
_this.parameter.passRequired = true |
|||
}else{ |
|||
// _this.parameter.noPassMsg = res.msg |
|||
uni.showModal({ |
|||
title: '报名需要补充如下个人信息', |
|||
content: res.msg, |
|||
success(res) { |
|||
if (res.confirm) { |
|||
uni.navigateTo({ |
|||
url: '../me/RealInfo?sysUserSid=' + getApp().globalData.sysUserSid |
|||
}) |
|||
}else |
|||
{ |
|||
return |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
}); |
|||
} |
|||
// 获取参数 |
|||
console.log(id) |
|||
console.log(info) |
|||
let activityItemSid = _this.page.activityDetails.listActivityItem[id].sid // 活动项目SId |
|||
let enrollMold = _this.page.activityDetails.listActivityItem[id].enrollMold // 报名模式(0为个人,1为团体) |
|||
console.log("用户SID",getApp().globalData.sysUserSid) |
|||
// 活动项目为个人 |
|||
if (enrollMold == 0){ |
|||
// 校验报名条件 |
|||
let passCondition = true |
|||
let sex = _this.page.activityDetails.listActivityItem[id].activityItemCondition.sex |
|||
let minAge = _this.page.activityDetails.listActivityItem[id].activityItemCondition.minAge |
|||
let maxAge = _this.page.activityDetails.listActivityItem[id].activityItemCondition.maxAge |
|||
if (sex > 0 || minAge > 0 || maxAge > 0){ |
|||
_this.HTTP({ |
|||
url: 'aos/v1/activityItem/verifyPersonalEnrollCondition', |
|||
method: 'GET', |
|||
paramsType: "FORM", |
|||
data: { |
|||
'activityItemSid': activitySid, |
|||
'sysUserSid': sysUserSid |
|||
}, |
|||
loading: true |
|||
}).then((res) => { |
|||
if ( res.code != "200") { |
|||
return |
|||
uni.showToast({ |
|||
title: '报名条件不符', |
|||
content:res.msg, |
|||
duration:3000 |
|||
}) |
|||
|
|||
} |
|||
}) |
|||
} |
|||
this.HTTP({ |
|||
url: 'aos/v1/activityItem/personalEnroll', |
|||
method: 'POST', |
|||
data: { |
|||
'activityItemSid': activityItemSid, |
|||
'sysUserSid': getApp().globalData.sysUserSid, |
|||
'paymentMemberSid': getApp().globalData.sysUserSid, |
|||
}, |
|||
paramsType: "JSON", |
|||
loading: true |
|||
}).then((res) => { |
|||
if (res.code == 200) { |
|||
this.Toast("报名成功!") |
|||
} else { |
|||
this.Toast(res.msg) |
|||
} |
|||
}, (err) => { |
|||
// 错误提示 |
|||
_this.Toast("出错了:" + err.data.errmsg) |
|||
}) |
|||
} |
|||
return |
|||
// 活动项目为团体 |
|||
if (enrollMold == 1){ |
|||
// 获取我的队伍 |
|||
|
|||
uni.navigateTo({ |
|||
url: "../find/TeamEnroll?activityItemSid="+activityItemSid |
|||
}) |
|||
} |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import url("../../static/columns.css"); |
|||
.items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
.agreeMent { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
margin-top: 50rpx; |
|||
margin-left: 30rpx; |
|||
|
|||
.text2 { |
|||
color: #666666; |
|||
font-size: 30rpx; |
|||
} |
|||
} |
|||
.content{ |
|||
display: flex; |
|||
flex-direction: column; |
|||
background: #FFFFFF; |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
</style> |
@ -0,0 +1,255 @@ |
|||
<template> |
|||
<RefreshView id="mescrollRef" ref="mescrollRef" :pageBg="F1F2F5" @refresh="refresh" text="活动列表" :useDownScroll="true" :useUpScroll="true"> |
|||
<sl-filter id="header" ref="slLilter" v-if="menuList.length!=0" :color="fd6d2a" themeColor="#000000" :menuList.sync="menuList" @result="result"></sl-filter> |
|||
<view v-for="(item,index) in page.listActivity " :key="index"> |
|||
<view class="activity-area" @click="clickItem(index)"> |
|||
<view class="summary"> |
|||
<view class=".name-category"> |
|||
<text class="name">{{item.name}}</text> |
|||
<text class="category">{{item.sportCategoryName}}</text> |
|||
</view> |
|||
<text class="enroll">报名截止:{{item.enrollEndTime}}</text> |
|||
</view> |
|||
|
|||
<view class="image-area"> |
|||
<image class="image" mode="aspectFill" :src="item.firstCoverImage"></image> |
|||
<view class="tips"> |
|||
<image class="icon" src="../../static/img/public/renqi.png"></image> |
|||
<text class="popularity">{{item.popularity}}</text> |
|||
<text :class="{'enroll-state enroll-no':item.enrollState ==1||item.enrollState ==3,'enroll-state enroll-yes':item.enrollState==2}" >{{item.enrollStateName}}</text> |
|||
</view> |
|||
</view> |
|||
<view v-for="(info,pos) in item.listActivityItemsArea " :key="pos"> |
|||
<view class="item-area"> |
|||
<view class="item-name-mold-money"> |
|||
<text class="item-name">{{info.name}}</text> |
|||
<text class="mold-money">{{info.enrollMoldName}}/{{info.enrollMoney==0?"免费":info.enrollMoney}}</text> |
|||
</view> |
|||
<view class="enroll-amount-date"> |
|||
<text class="amount">报名数:{{info.enrollNumbers==0?"暂无":info.enrollNumbersLimit+'人'}}{{info.enrollNumbersLimit==0?"不限":info.enrollNumbersLimit+'人'}}</text> |
|||
<text class="date">{{info.startTime}}</text> |
|||
</view> |
|||
</view> |
|||
<view v-if="item.listActivityItemsArea.length>pos+1" class="line-thin margin-top20"></view> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
</RefreshView> |
|||
</template> |
|||
|
|||
<script> |
|||
// 必须 |
|||
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js"; |
|||
export default { |
|||
// 使用mixin (在main.js注册全局组件) 必须 |
|||
mixins: [MescrollMixin], |
|||
data() { |
|||
return { |
|||
index: 0, |
|||
list: [], |
|||
page: { |
|||
'sort': 0, |
|||
'city': this.ReadPreference("find_city"), |
|||
'adCode': this.ReadPreference("find_city_code").slice(0, -2), |
|||
state: "", |
|||
listActivity:[] |
|||
}, |
|||
menuList: [{ |
|||
'title': '当前定位城市', |
|||
'key': 'type', |
|||
'reflexTitle': true, |
|||
'defaultSelectedIndex': 0, |
|||
detailList: [{ |
|||
'title': this.ReadPreference("find_city"), |
|||
'value': this.ReadPreference("find_city_code").slice(0, -2) |
|||
}, |
|||
{ |
|||
'title': '选择其他城市', |
|||
'value': '0' |
|||
} |
|||
] |
|||
}, { |
|||
'title': '默认活动类型', |
|||
'key': 'state', |
|||
'reflexTitle': true, |
|||
'defaultSelectedIndex': 0, |
|||
detailList: [{ |
|||
'title': '全部活动', |
|||
'value': "" |
|||
}] |
|||
}, { |
|||
'title': '默认排序', |
|||
'key': 'sort', |
|||
'reflexTitle': true, |
|||
'defaultSelectedIndex': 0, |
|||
'detailList': [{ |
|||
'title': '按发布时间排序', |
|||
'value': 0 |
|||
}, |
|||
{ |
|||
'title': '按报名时间排序', |
|||
'value': 1 |
|||
}, |
|||
{ |
|||
'title': '按人气从高到低', |
|||
'value': 2 |
|||
} |
|||
] |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
onLoad() { |
|||
let that = this |
|||
that.HTTP({ |
|||
url: 'aos/v1/activity/getActivityCreatePageParameter', |
|||
method: 'GET', |
|||
data: { |
|||
"adcode": that.page.adCode |
|||
}, |
|||
paramsType: "FORM", |
|||
loading: true |
|||
}).then((res) => { |
|||
console.log("that.menuList[1].detailList", that.menuList[1].detailList); |
|||
if (200 == res.code) { |
|||
// 为活动类型块查询条件赋值(直接获取活动类别接口) |
|||
let listSportCategoryArea = []; |
|||
for (var i = 0; i < res.data.listSportCategoryArea.length; i++) { |
|||
listSportCategoryArea.push({ |
|||
title: res.data.listSportCategoryArea[i].sportCategoryName, |
|||
value: res.data.listSportCategoryArea[i].sid |
|||
}) |
|||
} |
|||
// 必须用变量去接受 |
|||
that.menuList[1].detailList = that.menuList[1].detailList.concat(listSportCategoryArea) |
|||
this.$refs.slLilter.setTitle(this.menuList) |
|||
} |
|||
}); |
|||
}, |
|||
onShow() { |
|||
let backResult = this.OnActivityResult(); |
|||
if (backResult != undefined) { |
|||
if (!this.IsEmpty(backResult.code)) { |
|||
console.log("结果2>" + backResult.code) |
|||
this.page.adCode = backResult.code |
|||
this.page.city = backResult.city |
|||
this.menuList[0].detailList[0].title = backResult.city |
|||
this.menuList[0].detailList[0].value = backResult.code |
|||
console.log("qqq", this.menuList[0].detailList); |
|||
this.$refs.slLilter.setTitle(this.menuList) |
|||
this.$refs.mescrollRef.resetPageOne(); |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
result(val) { |
|||
|
|||
if (!this.IsEmpty(val.sort)) { |
|||
this.page.sort = val.sort; |
|||
this.list = [] |
|||
console.log("this.page.sort", this.page.sort); |
|||
} |
|||
if (!this.IsEmpty(val.type)) { |
|||
if (val.type != 0) { |
|||
this.page.game = val.type; |
|||
this.list = [] |
|||
} else { |
|||
console.log("===>1" + JSON.stringify(this.page)) |
|||
uni.navigateTo({ |
|||
url: "../../pages/city/CitySelectActivity?city=" + this.page.city + |
|||
"&code=" + this.page.adCode |
|||
}) |
|||
} |
|||
} |
|||
|
|||
if (!this.IsEmpty(val.state)) { |
|||
this.page.state = val.state; |
|||
this.list = [] |
|||
console.log("this.page.state", this.page.state); |
|||
} |
|||
|
|||
this.$refs.mescrollRef.resetPageOne(); |
|||
|
|||
}, |
|||
refresh(page) { |
|||
let _this = this |
|||
this.HTTP({ |
|||
url: 'aos/v1/activity/getActivityAreaPagerList', |
|||
paramsType: "JSON", |
|||
method: "POST", |
|||
data: { |
|||
current: page.num, |
|||
size: 10, |
|||
params: { |
|||
name: "", |
|||
adCode: _this.menuList[0].detailList[0].value, |
|||
orderBy: _this.page.sort, |
|||
sportCategorySid: _this.page.state |
|||
} |
|||
}, |
|||
loading: true |
|||
}).then((res) => { |
|||
// 成功关闭刷新状态 |
|||
_this.$refs.mescrollRef.refreshFinished(res.data.records.length) |
|||
if (page.num == 1) { |
|||
// 第一页 先清空集合数据 |
|||
_this.page.listActivity = [] |
|||
} |
|||
// 追加数据 |
|||
_this.page.listActivity = _this.page.listActivity.concat(res.data.records) |
|||
|
|||
}).catch(function(err) { |
|||
_this.$refs.mescrollRef.refreshError() |
|||
}); |
|||
}, |
|||
clickItem(index) { |
|||
let activitySid = this.page.listActivity[index].sid |
|||
console.log(activitySid) |
|||
uni.navigateTo({ |
|||
url: '../find/ActivityDetail?activitySid=' + activitySid |
|||
}); |
|||
|
|||
}, |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import url("../../static/columns.css"); |
|||
.top { |
|||
|
|||
display: flex; |
|||
flex-direction: row; |
|||
height: 100rpx; |
|||
background: #FFFFFF; |
|||
opacity: 0.8; |
|||
align-items: center; |
|||
border-radius: 20rpx; |
|||
margin-top: 20rpx; |
|||
margin-left: 30rpx; |
|||
margin-right: 30rpx; |
|||
|
|||
.unselected { |
|||
|
|||
text-align: center; |
|||
font-size: 28rpx; |
|||
font-family: Adobe Heiti Std; |
|||
font-weight: normal; |
|||
color: #191919; |
|||
line-height: 40rpx; |
|||
|
|||
} |
|||
.selected { |
|||
text-align: center; |
|||
font-size: 28rpx; |
|||
font-family: Adobe Heiti Std; |
|||
font-weight: normal; |
|||
color: #2CAB69; |
|||
line-height: 40rpx; |
|||
border-bottom: 2rpx #2CAB69 solid; |
|||
padding-bottom: 10rpx; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,136 @@ |
|||
<template> |
|||
<view> |
|||
|
|||
<RefreshView ref="mescrollRef" @refresh="refresh" :hasBack="true" text="报名列表"> |
|||
|
|||
<view v-for="(item,index) in data.list" :key='index' class="list-item-layout"> |
|||
|
|||
<image :src="item.headImage" class="list-item-img" mode="aspectFill"> |
|||
</image> |
|||
|
|||
<view class="list-item-right"> |
|||
<view class="list-item-name">{{item.userNickName}}</view> |
|||
<view class="list-item-content">报名时间:{{item.enrollTime}}</view> |
|||
</view> |
|||
|
|||
</view> |
|||
</RefreshView> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// 必须 |
|||
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js"; |
|||
|
|||
export default { |
|||
// 使用mixin (在main.js注册全局组件) 必须 |
|||
mixins: [MescrollMixin], |
|||
data() { |
|||
return { |
|||
page: { |
|||
activitySid: '', |
|||
activityItemSid: '', |
|||
}, |
|||
data: { |
|||
list: [] |
|||
} |
|||
|
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
// 赋值 |
|||
this.page.activitySid = options.activitySid |
|||
this.page.activityItemSid = options.activityItemSid |
|||
|
|||
}, |
|||
methods: { |
|||
refresh(page) { |
|||
|
|||
let _this = this; |
|||
|
|||
this.HTTP({ |
|||
url: 'aos/v1/participantRelation/getPageListPersonalParticipant', |
|||
paramsType: "JSON", |
|||
method: "POST", |
|||
data: { |
|||
current: page.num, |
|||
size: 10, |
|||
params: { |
|||
activitySid: this.page.activitySid, |
|||
activityItemSid: this.page.activityItemSid , |
|||
} |
|||
}, |
|||
loading: true |
|||
}).then((res) => { |
|||
|
|||
// 成功关闭刷新状态 |
|||
_this.$refs.mescrollRef.refreshFinished(res.data.records.length) |
|||
|
|||
if (page.num == 1) { |
|||
// 第一页 先清空集合数据 |
|||
_this.data.list = [] |
|||
|
|||
} |
|||
|
|||
// 追加数据 |
|||
_this.data.list = _this.data.list.concat(res.data.records) |
|||
|
|||
}).catch(function(err) { |
|||
console.log("11111"); |
|||
_this.$refs.mescrollRef.refreshError() |
|||
}) |
|||
}, |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.list-item-layout { |
|||
display: flex; |
|||
width: 100%; |
|||
flex-direction: row; |
|||
box-sizing: border-box; |
|||
padding-top: 20rpx; |
|||
padding-bottom: 10rpx; |
|||
margin-left: 33rpx; |
|||
margin-right: 33rpx; |
|||
border-bottom: 0.1px #F1F1F1 solid; |
|||
|
|||
.list-item-img { |
|||
width: 140rpx; |
|||
height: 140rpx; |
|||
margin-right: 25rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.list-item-right { |
|||
flex: 1; |
|||
margin-top: 20rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 140rpx; |
|||
|
|||
.list-item-name { |
|||
width: 100%; |
|||
flex: 1; |
|||
color: #333333; |
|||
font-size: 27rpx; |
|||
} |
|||
|
|||
.list-item-content { |
|||
width: 100%; |
|||
flex: 1; |
|||
color: #999999; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.list-item-time { |
|||
width: 100%; |
|||
color: #999999; |
|||
font-size: 24rpx; |
|||
flex: 1; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,217 @@ |
|||
<template> |
|||
<RefreshView ref="mescrollRef" :hasBack="true" text="队伍报名" :useDownScroll="false" :useUpScroll="false"> |
|||
<view class="top"> |
|||
<text class="top_lift">选中({{page.selectedMemberAmount}})</text> |
|||
<text class="top_right" @click="enroll()" >比赛报名</text> |
|||
</view> |
|||
<view class="line"></view> |
|||
<view> |
|||
<view v-for="(item,index) in page.listTeamMemberArea " :key="index"> |
|||
<view class="item"> |
|||
<view class="line" style="height: 1px;"></view> |
|||
<view class="item_content"> |
|||
<checkbox-group class="item_check" @change="checkboxChange($event, index)"> |
|||
<checkbox :checked="item.checked"></checkbox> |
|||
</checkbox-group> |
|||
<image class="item_img" :src="item.headImage" mode="aspectFill"></image> |
|||
<text class="item_text">{{item.realName}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</RefreshView> |
|||
</template> |
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
data:{ |
|||
activityItemSid:"", // 活动项目Sid |
|||
teamSid:"", // 队伍Sid |
|||
listTeamMemberSid:[], // 队伍成员Sid列表 |
|||
paymentMemberSid:"" // 付费会员Sid |
|||
}, |
|||
page:{ |
|||
type: 1, // 队伍类型1为我创建的 |
|||
listTeamMemberArea:[], // 会员块列表 |
|||
selectedMemberAmount:0 // 选中成员数 |
|||
} |
|||
} |
|||
}, |
|||
onLoad:function(options) { |
|||
// 接收队伍Sid |
|||
this.data.activityItemSid = options.activityItemSid |
|||
// 判断是否登陆 |
|||
if (!getApp().globalData.isLogin){ |
|||
// 进入登陆页面 |
|||
} |
|||
let sysUserSid = getApp().globalData.sysUserSid |
|||
let _teamSid ="" |
|||
// 获取我的队伍,如果为一个队伍在当前页,如果多个队伍则进入选择界面 |
|||
|
|||
let _this = this |
|||
_this.HTTP({ |
|||
url: 'aos/v1/aosUser/getMyTeamAreaPagerList', |
|||
paramsType: "JSON", |
|||
method: "POST", |
|||
data: { |
|||
current: 1, |
|||
size: 10, |
|||
params: { |
|||
sysUserSid: sysUserSid, |
|||
type: 1 |
|||
} |
|||
}, |
|||
loading: true |
|||
}) |
|||
.then((res) => { |
|||
// 未找到队伍返回 |
|||
if (!res.success){ |
|||
return |
|||
} |
|||
let listTeamArea = res.data.records |
|||
if (listTeamArea.length > 1 ){ |
|||
console.log('队伍数大于1支跳转:', listTeamArea) |
|||
} |
|||
// 设置队伍Sid |
|||
_this.data.teamSid = listTeamArea[0].sid |
|||
console.log('最新的队伍Sid', _this.data.teamSid) |
|||
// 获取列表 |
|||
_this.HTTP({ |
|||
url: 'aos/v1/aosUser/getTeamArea', |
|||
paramsType: "FORM", |
|||
method: "GET", |
|||
data: { |
|||
teamSid: _this.data.teamSid |
|||
}, |
|||
toast: true, |
|||
loading: true |
|||
}) |
|||
.then((res) => { |
|||
console.log('res', res) |
|||
_this.page.listTeamMemberArea = res.data.listTeamMemberArea |
|||
}); |
|||
|
|||
|
|||
}, (err) => { |
|||
// 错误提示 |
|||
_this.Toast("出错了:" + err.data.errmsg) |
|||
}) |
|||
}, |
|||
methods: { |
|||
enroll() { |
|||
// 保存 |
|||
let _this = this |
|||
console.log('=====', _this) |
|||
let _activityItemSid = this.data.activityItemSid |
|||
|
|||
let _teamSid = _this.data.teamSid |
|||
let _listTeamMemberSid = _this.data.listTeamMemberSid |
|||
_this.HTTP({ |
|||
url: 'aos/v1/activityItem/teamEnroll', |
|||
paramsType: "JSON", |
|||
method: "POST", |
|||
data: { |
|||
'activityItemSid':_activityItemSid, |
|||
'teamSid': _teamSid, |
|||
'listTeamMemberSid': _listTeamMemberSid |
|||
}, |
|||
loading: true |
|||
}) |
|||
.then((res) => { |
|||
uni.navigateBack({ |
|||
delta: 1 |
|||
}) |
|||
}); |
|||
}, |
|||
checkboxChange(e, index) { |
|||
// 切换选择 |
|||
this.page.listTeamMemberArea[index].checked = !this.page.listTeamMemberArea[index].checked |
|||
console.log("listTeamMemberArea" + JSON.stringify(this.page.listTeamMemberArea[0])) |
|||
// 勾选后添加Sid,取消勾选后删除 |
|||
if (this.page.listTeamMemberArea[index].checked) { |
|||
this.data.listTeamMemberSid.push(this.page.listTeamMemberArea[index].sid) |
|||
} |
|||
else { |
|||
// 删除当前的Sid |
|||
let sid = this.page.listTeamMemberArea[index].sid |
|||
let pos = this.data.listTeamMemberSid.findIndex((x) => { |
|||
return x == sid |
|||
}) |
|||
this.data.listTeamMemberSid.splice(pos, 1) |
|||
} |
|||
this.page.selectedMemberAmount = this.data.listTeamMemberSid.length |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import url("../../static/master.css"); |
|||
.line { |
|||
height: 10rpx; |
|||
background: #eee; |
|||
width: 100%; |
|||
} |
|||
|
|||
.top { |
|||
display: flex; |
|||
background: #fff; |
|||
flex-direction: row; |
|||
width: 100%; |
|||
padding-top: 26rpx; |
|||
padding-bottom: 26rpx; |
|||
|
|||
.top_lift { |
|||
margin-left: 30rpx; |
|||
font-weight: 550; |
|||
font-family: sans-serif; |
|||
flex: 1; |
|||
color: #101010; |
|||
font-size: 36rpx; |
|||
} |
|||
|
|||
.top_right { |
|||
margin-right: 30rpx; |
|||
background-color: #F4CE98; |
|||
color: #fff; |
|||
font-size: 24rpx; |
|||
padding: 10rpx 20rpx; |
|||
text-align: right; |
|||
} |
|||
} |
|||
|
|||
.item { |
|||
|
|||
display: flex; |
|||
flex-direction: column; |
|||
|
|||
.item_content { |
|||
display: flex; |
|||
align-items: center; |
|||
background-color: #fff; |
|||
padding: 20rpx 32rpx; |
|||
flex-direction: row; |
|||
|
|||
.item_check { |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
} |
|||
|
|||
.item_img { |
|||
margin-left: 40rpx; |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 10rpx; |
|||
} |
|||
|
|||
.item_text { |
|||
font-size: 28rpx; |
|||
color: #101010; |
|||
margin-left: 40rpx; |
|||
} |
|||
} |
|||
|
|||
} |
|||
</style> |
@ -0,0 +1,364 @@ |
|||
<template> |
|||
<view> |
|||
<RefreshView ref="mescrollRef" text="首页" :useDownScroll="false" :useUpScroll="false" pageBg="#F1F2F5"> |
|||
|
|||
<view style="display: flex;flex-direction: column;align-items: center;width: 100vw;"> |
|||
|
|||
<view style="display: flex;flex-direction: column;width: 100vw;align-items: center;background-color: #2fa1f0;padding-top: 80rpx; |
|||
border-bottom-right-radius: 30rpx;border-bottom-left-radius: 30rpx;"> |
|||
|
|||
<view style="width: 100vw;text-align: center;font-size: 40px;font-weight: 700;color: #fff;" > |
|||
<!-- <view style="left: 100px; |
|||
top: 79px; |
|||
font-weight: 700; |
|||
width: 160px; |
|||
height: 58px; |
|||
color: rgba(255, 255, 255, 100); |
|||
font-size: 40px; |
|||
text-align: center; |
|||
font-family: SourceHanSansSC-bold;">--> |
|||
体育活动 |
|||
</view> |
|||
<view |
|||
style="width: 100vw;text-align: center;font-size: 22px;font-weight: 600;color: #fff;margin-top: 10rpx;"> |
|||
<!-- <view style="left: 114px; |
|||
top: 139px; |
|||
font-weight: 600; |
|||
width: 132px; |
|||
height: 32px; |
|||
color: rgba(255, 255, 255, 100); |
|||
font-size: 22px; |
|||
text-align: center; |
|||
font-family: SourceHanSansSC-bold;"> --> |
|||
组织报名工具 |
|||
</view> |
|||
|
|||
<text style="margin-top: 80rpx;color:#fff;font-size: 16px;">一分钟创建 轻松组织活动</text> |
|||
|
|||
<view |
|||
style="display: flex;flex-direction: row;margin-top: 40rpx;justify-content: center;margin-bottom: 100rpx;align-items: center;"> |
|||
|
|||
<image src="../../static/home-icon/visits.png" style="width: 35rpx;height: 35rpx;"></image> |
|||
<text style="margin-left: 20rpx;color: #fff;font-size: 12px;">访问量:{{visits}}</text> |
|||
|
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<view style="display: flex;flex-direction: row;margin-top: 60rpx;margin-left: 30rpx;align-items:baseline"> |
|||
<text style="font-weight: 400; color: #E99D42 ;font-size: 36rpx;">发布活动类别</text> |
|||
<text style="margin-left: 20rpx;font-weight: 400; color: #E99D42;font-size: 28rpx;">(点击体育活动标签发布)</text> |
|||
|
|||
</view> |
|||
|
|||
<view style="display: flex; margin-left: 10rpx;margin-right: 30rpx;margin-top: 40rpx; |
|||
justify-content: center;"> |
|||
<grid> |
|||
<view style="display: flex; margin-bottom: 10rpx; margin-left: 10rpx;margin-right: 10rpx;" |
|||
v-for="(item,index) in SportCategoryList"> |
|||
<text class="labelLayout" @click="onClick(index)">{{item.sportCategoryName}}</text> |
|||
</view> |
|||
</grid> |
|||
</view> |
|||
|
|||
<view class="line" style=" margin-top: 100rpx;margin-left: 30rpx;margin-right: 30rpx;"></view> |
|||
|
|||
<view |
|||
style="display: flex;flex-direction: column;margin-top: 40rpx;margin-bottom: 20rpx;margin-left: 30rpx;margin-right: 30rpx;"> |
|||
<text style="font-size: 32rpx;color: #666666;font-weight: 600">友情提示:</text> |
|||
<text style="font-size: 30rpx;color: #666666;margin-top: 40rpx;font-weight: 500;">{{notices}}</text> |
|||
|
|||
</view> |
|||
|
|||
<view class="line" style=" margin-left: 30rpx;margin-right: 30rpx;margin-top: 40rpx;"></view> |
|||
|
|||
<view style="margin-top: 30rpx;margin-left: 30rpx;margin-right: 30rpx;"> |
|||
|
|||
<text style="font-size: 40rpx;color: #4095E5;font-weight: 600" @click="jump()">{{page.city}}</text> |
|||
<text |
|||
style="font-size: 32rpx;color: #666666;font-weight: 500;margin-left: 20rpx;">共有{{activityAmount}}场活动</text> |
|||
</view> |
|||
<view style="height: 150px;"></view> |
|||
</RefreshView> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// 必须 |
|||
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js"; |
|||
export default { |
|||
// 使用mixin (在main.js注册全局组件) 必须 |
|||
mixins: [MescrollMixin], |
|||
data() { |
|||
return { |
|||
index: 0, |
|||
notices: "\u3000\u3000您注册的帐号可以在宇运动主体下的子平台一号通行,同时您发布的活动将会按类别发布到各子平台。(点击查看宇运动子平台)", |
|||
list: [], |
|||
SportCategoryList: [], |
|||
page: { |
|||
city: "正在获取...", |
|||
code: "" |
|||
}, |
|||
visits: "...", |
|||
activityAmount: "..." |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
this.HTTP({ |
|||
url: 'aos/v1/activityManagement/add1PointOnVisits', |
|||
method: 'GET', |
|||
data: {}, |
|||
paramsType: "FORM", |
|||
loading: false |
|||
}).then((res) => { |
|||
|
|||
}); |
|||
let that = this |
|||
|
|||
let find_city_code = this.ReadPreference("find_city_code") |
|||
let find_city = this.ReadPreference("find_city") |
|||
|
|||
if (this.IsEmpty(find_city_code)) { |
|||
// 首次进入 |
|||
find_city_code = 0; |
|||
|
|||
// 获取定位 |
|||
let _this = this; |
|||
// 获取定位 |
|||
uni.getLocation({ |
|||
type: 'gcj02', |
|||
success: function(res) { |
|||
|
|||
let locat = res |
|||
console.log("latitude", res.longitude); |
|||
console.log("longitude", res.latitude); |
|||
// 先写入定位 |
|||
_this.location = { |
|||
latitude: locat.latitude, |
|||
longitude: locat.longitude |
|||
} |
|||
|
|||
let url = |
|||
"https://restapi.amap.com/v3/geocode/regeo?key=59970402d1c3f7dc1efff17d4dfcff21&location=" + |
|||
res.longitude + "," + res.latitude + |
|||
"&poitype=&radius=1000&extensions=all&batch=false&roadlevel=0"; |
|||
|
|||
_this.HttpOtherUrl({ |
|||
url: url |
|||
}).then((res) => { |
|||
|
|||
let json = JSON.stringify(res); |
|||
let info = JSON.parse(json).regeocode.addressComponent; |
|||
// 城市 |
|||
let city = info.city; |
|||
// 城市编码 |
|||
let code = info.adcode; |
|||
|
|||
// 获取到正确的城市编码 |
|||
if (code > 0) { |
|||
console.log("find_city1", city); |
|||
console.log("find_city_code1", code); |
|||
// 保存本次定位 |
|||
_this.WritePreference("find_city", city) |
|||
_this.WritePreference("find_city_code", code) |
|||
|
|||
_this.page.code = code |
|||
_this.page.city = city |
|||
_this.getCode() |
|||
} else { |
|||
console.log("find_city2", city); |
|||
console.log("find_city_code2", code); |
|||
// 未获取到正确的城市编码 |
|||
_this.WritePreference("find_city", "石家庄") |
|||
_this.WritePreference("find_city_code", "130104") |
|||
|
|||
_this.page.code = "130104" |
|||
_this.page.city = "石家庄" |
|||
_this.getCode() |
|||
} |
|||
}, (err) => { |
|||
console.log("find_city3", "石家庄"); |
|||
console.log("find_city_code3", "130104"); |
|||
//获取当前城市失败 |
|||
_this.WritePreference("find_city", "石家庄") |
|||
_this.WritePreference("find_city_code", "130104") |
|||
|
|||
_this.page.code = "130104" |
|||
_this.page.city = "石家庄" |
|||
_this.getCode() |
|||
|
|||
}) |
|||
}, |
|||
fail(err) { |
|||
console.log("find_city4", "石家庄"); |
|||
console.log("find_city_code4", "130104"); |
|||
// 定位失败 |
|||
_this.WritePreference("find_city", "石家庄") |
|||
_this.WritePreference("find_city_code", "130104") |
|||
|
|||
_this.page.code = "130104" |
|||
_this.page.city = "石家庄" |
|||
_this.getCode() |
|||
} |
|||
}); |
|||
|
|||
} else { |
|||
this.page.code = find_city_code |
|||
this.page.city = find_city |
|||
this.getCode() |
|||
} |
|||
|
|||
}, |
|||
|
|||
onShow() { |
|||
let find_city_code = this.ReadPreference("find_city_code") |
|||
|
|||
let find_city = this.ReadPreference("find_city") |
|||
|
|||
if (!this.IsEmpty(find_city_code)) { |
|||
this.getCode() |
|||
} |
|||
|
|||
}, |
|||
methods: { |
|||
// wwww(){ |
|||
// uni.navigateTo({ |
|||
// url: "../team/CreateTeam" |
|||
// }) |
|||
// }, |
|||
getCode() { |
|||
let that = this |
|||
that.HTTP({ |
|||
url: 'aos/v1/activityManagement/getActivityCreatePageParameter', |
|||
method: 'GET', |
|||
data: { |
|||
"adcode": this.page.code |
|||
}, |
|||
paramsType: "FORM", |
|||
loading: true |
|||
}).then((res) => { |
|||
that.SportCategoryList = res.data.listSportCategoryArea |
|||
that.activityAmount = res.data.activityAmount |
|||
that.visits = res.data.visits |
|||
|
|||
}); |
|||
}, |
|||
onClick(index) { |
|||
uni.navigateTo({ |
|||
url: "../index/activity?sportCategoryName=" + this.SportCategoryList[index].sportCategoryName + |
|||
"&sportCategorySid=" + this.SportCategoryList[index].sid + "&isCreate=0&sid=" |
|||
}) |
|||
}, |
|||
jump() { |
|||
console.log('this.page.code', this.page.code) |
|||
uni.switchTab({ |
|||
url: "../home/FindFragment" |
|||
}) |
|||
} |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.line { |
|||
height: 5rpx; |
|||
background-color: #eee |
|||
} |
|||
|
|||
.btn { |
|||
display: flex; |
|||
width: 80%; |
|||
height: 80rpx; |
|||
flex-direction: column; |
|||
background-color: $uni-base-color; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
align-items: center; |
|||
justify-content: center; |
|||
border-radius: 10rpx; |
|||
} |
|||
|
|||
.nav-bar { |
|||
height: 92rpx; |
|||
width: 100%; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
|
|||
.bar-text { |
|||
font-size: 32rpx; |
|||
height: 100%; |
|||
line-height: 92rpx; |
|||
padding-left: 28rpx; |
|||
} |
|||
|
|||
.bar-img { |
|||
width: 90rpx; |
|||
height: 92rpx; |
|||
padding-right: 28rpx; |
|||
} |
|||
} |
|||
|
|||
.top { |
|||
|
|||
display: flex; |
|||
flex-direction: row; |
|||
height: 100rpx; |
|||
background: #FFFFFF; |
|||
opacity: 0.8; |
|||
align-items: center; |
|||
border-radius: 20rpx; |
|||
margin-top: 20rpx; |
|||
margin-left: 30rpx; |
|||
margin-right: 30rpx; |
|||
|
|||
.unselected { |
|||
|
|||
text-align: center; |
|||
font-size: 28rpx; |
|||
font-family: Adobe Heiti Std; |
|||
font-weight: normal; |
|||
color: #191919; |
|||
line-height: 40rpx; |
|||
|
|||
} |
|||
|
|||
.selected { |
|||
text-align: center; |
|||
font-size: 28rpx; |
|||
font-family: Adobe Heiti Std; |
|||
font-weight: normal; |
|||
color: #2CAB69; |
|||
line-height: 40rpx; |
|||
border-bottom: 2rpx #2CAB69 solid; |
|||
padding-bottom: 10rpx; |
|||
} |
|||
|
|||
} |
|||
|
|||
.btn1 { |
|||
background: #BBBBBB; |
|||
} |
|||
|
|||
.btn2 { |
|||
background: #0081D5; |
|||
} |
|||
|
|||
.labelLayout { |
|||
flex: 1; |
|||
width: 100%; |
|||
margin-left: 20rpx; |
|||
margin-right: 20rpx; |
|||
background-color: #F2BF5C; |
|||
color: #FFFFFF; |
|||
padding-top: 8rpx; |
|||
padding-bottom: 8rpx; |
|||
padding-left: 20rpx; |
|||
padding-right: 20rpx; |
|||
justify-content: center; |
|||
text-align: center; |
|||
font-size: 30rpx; |
|||
} |
|||
</style> |
@ -0,0 +1,326 @@ |
|||
<template> |
|||
<view style="background: #F1F2F5;"> |
|||
<RefreshView id="mescrollRef" ref="mescrollRef" :pageBg="F1F2F5" @refresh="refresh" text="活动列表" |
|||
:useDownScroll="true" :useUpScroll="true"> |
|||
|
|||
<sl-filter id="header" ref="slLilter" v-if="menuList.length!=0" :color="fd6d2a" themeColor="#000000" |
|||
:menuList.sync="menuList" @result="result"></sl-filter> |
|||
|
|||
<view> |
|||
<view v-for="(item,index) in list " :key="index"> |
|||
<view |
|||
style="display: flex;flex-direction: column;margin-bottom: 30rpx; padding: 30rpx;background: #FFFFFF;" |
|||
@click="clickItem(index)"> |
|||
<view style="display: flex;flex-direction: column;"> |
|||
<view style="display: flex; flex-direction: row;"> |
|||
<text style="color: #101010; font-size: 32rpx; display:-webkit-box;-webkit-line-clamp:1; |
|||
overflow:hidden;text-overflow:ellipsis;-webkit-box-orient:vertical; |
|||
word-break:break-all;flex: 1;">{{item.name}}</text> |
|||
<text |
|||
style="color: #fff; font-size: 24rpx; background-color: #F2BF5C; padding: 5rpx 10rpx;">{{item.sportCategoryName}}</text> |
|||
</view> |
|||
<text |
|||
style="color: #898989 ; font-size: 28rpx; margin-top: 10rpx;">报名截止:{{item.enrollEndTime}}</text> |
|||
</view> |
|||
<view style="width: 100%;height: 300rpx; margin-top: 20rpx;margin-bottom: 20rpx;"> |
|||
<image style="width: 100%;height: 100%; border-radius: 30rpx;" mode="aspectFill" |
|||
:src="item.firstCoverImage"></image> |
|||
|
|||
<view style="display: flex; height: 50rpx;width: 100%; margin-top: -80rpx; "> |
|||
<view style=" display: flex;align-items: center;width: 100%;"> |
|||
<image style="width: 28px;height: 48rpx;margin-left: 30rpx;" |
|||
src="../../static/renqi.png"></image> |
|||
<text style="color: #fff; margin-left: 15rpx;flex: 1;">{{item.popularity}}</text> |
|||
<text |
|||
:class="{'btn1':item.enrollState ==1||item.enrollState ==3,'btn2':item.enrollState==2}" |
|||
style="display: flex;text-align: center; padding: 8rpx 15rpx; |
|||
color: #FFFFFF; font-size: 28rpx; margin-right:30rpx; border-radius: 10rpx;">{{item.enrollStateName}}</text> |
|||
|
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<view v-for="(info,pos) in item.listActivityItemsArea " :key="pos"> |
|||
<view style="display: flex;flex-direction: column;"> |
|||
|
|||
<view |
|||
style="display: flex;flex-direction: row; align-items: center; margin-top: 10rpx;"> |
|||
<text style="color: #101010; font-size: 28rpx; flex: 1;">{{info.name}}</text> |
|||
<view style=" align-items: center;"> |
|||
|
|||
<text style="color: #ff0000 ; font-size: 24rpx;">{{info.enrollMoldName}}</text> |
|||
<text |
|||
style="color: #ff0000 ; font-size: 24rpx;margin-left: 10rpx;margin-right: 10rpx;">/</text> |
|||
<text |
|||
style="color: #ff0000 ; font-size: 24rpx;">{{info.enrollMoney==0?"免费":info.enrollMoney}}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view style="display: flex;flex-direction: row;margin-top: 5rpx;"> |
|||
<view style="flex: 1; align-items: center;"> |
|||
|
|||
<text style="color: #919191; font-size: 24rpx;">报名数:</text> |
|||
<text |
|||
style="color: #919191 ; font-size: 24rpx;">{{info.enrollNumbers==0?"暂无":info.enrollNumbersLimit+'人'}}</text> |
|||
<text |
|||
style="color: #919191 ; font-size: 24rpx;margin-left: 10rpx;margin-right: 10rpx;">/</text> |
|||
<text |
|||
style="color: #919191 ; font-size: 24rpx;">{{info.enrollNumbersLimit==0?"不限":info.enrollNumbersLimit+'人'}}</text> |
|||
</view> |
|||
|
|||
<text |
|||
style="margin-top: 10rpx; color: #919191; font-size: 24rpx;">{{info.startTime}}</text> |
|||
|
|||
</view> |
|||
|
|||
<view v-if="item.listActivityItemsArea.length>pos+1" |
|||
style="width: 100%; height: 2rpx;background-color: #efefef; margin-top: 10rpx;"> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
|
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
</RefreshView> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// 必须 |
|||
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js"; |
|||
|
|||
export default { |
|||
// 使用mixin (在main.js注册全局组件) 必须 |
|||
mixins: [MescrollMixin], |
|||
data() { |
|||
return { |
|||
index: 0, |
|||
list: [], |
|||
page: { |
|||
'sort': 0, |
|||
'city': this.ReadPreference("find_city"), |
|||
'adCode': this.ReadPreference("find_city_code").slice(0, -2), |
|||
state: "" |
|||
}, |
|||
menuList: [{ |
|||
'title': '当前定位城市', |
|||
'key': 'type', |
|||
'reflexTitle': true, |
|||
'defaultSelectedIndex': 0, |
|||
detailList: [{ |
|||
'title': this.ReadPreference("find_city"), |
|||
'value': this.ReadPreference("find_city_code").slice(0, -2) |
|||
}, |
|||
{ |
|||
'title': '选择其他城市', |
|||
'value': '0' |
|||
} |
|||
] |
|||
}, { |
|||
'title': '默认活动类型', |
|||
'key': 'state', |
|||
'reflexTitle': true, |
|||
'defaultSelectedIndex': 0, |
|||
detailList: [{ |
|||
'title': '全部活动', |
|||
'value': "" |
|||
}] |
|||
}, { |
|||
'title': '默认排序', |
|||
'key': 'sort', |
|||
'reflexTitle': true, |
|||
'defaultSelectedIndex': 0, |
|||
'detailList': [{ |
|||
'title': '按发布时间排序', |
|||
'value': 0 |
|||
}, |
|||
{ |
|||
'title': '按报名时间排序', |
|||
'value': 1 |
|||
}, |
|||
{ |
|||
'title': '按人气从高到低', |
|||
'value': 2 |
|||
} |
|||
] |
|||
} |
|||
|
|||
] |
|||
} |
|||
|
|||
}, |
|||
onLoad() { |
|||
let that = this |
|||
that.HTTP({ |
|||
url: 'aos/v1/activityManagement/getActivityCreatePageParameter', |
|||
method: 'GET', |
|||
data: { |
|||
"adcode": that.page.adCode |
|||
}, |
|||
paramsType: "FORM", |
|||
loading: true |
|||
}).then((res) => { |
|||
console.log("that.menuList[1].detailList", that.menuList[1].detailList); |
|||
if (200 == res.code) { |
|||
let list = []; |
|||
|
|||
for (var i = 0; i < res.data.listSportCategoryArea.length; i++) { |
|||
list.push({ |
|||
title: res.data.listSportCategoryArea[i].sportCategoryName, |
|||
value: res.data.listSportCategoryArea[i].sid |
|||
}) |
|||
} |
|||
// 必须用变量去接受 |
|||
that.menuList[1].detailList = that.menuList[1].detailList.concat(list) |
|||
|
|||
this.$refs.slLilter.setTitle(this.menuList) |
|||
console.log("qqq", that.menuList[1].detailList); |
|||
} |
|||
}); |
|||
}, |
|||
onShow() { |
|||
|
|||
let backResult = this.OnActivityResult(); |
|||
if (backResult != undefined) { |
|||
console.log("结果>" + JSON.stringify(backResult)) |
|||
if (!this.IsEmpty(backResult.code)) { |
|||
console.log("结果2>" + backResult.code) |
|||
this.page.adCode = backResult.code |
|||
this.page.city = backResult.city |
|||
this.menuList[0].detailList[0].title = backResult.city |
|||
this.menuList[0].detailList[0].value = backResult.code |
|||
console.log("qqq", this.menuList[0].detailList); |
|||
this.$refs.slLilter.setTitle(this.menuList) |
|||
this.$refs.mescrollRef.resetPageOne(); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
result(val) { |
|||
|
|||
if (!this.IsEmpty(val.sort)) { |
|||
this.page.sort = val.sort; |
|||
this.list = [] |
|||
console.log("this.page.sort", this.page.sort); |
|||
} |
|||
if (!this.IsEmpty(val.type)) { |
|||
if (val.type != 0) { |
|||
this.page.game = val.type; |
|||
this.list = [] |
|||
} else { |
|||
console.log("===>1" + JSON.stringify(this.page)) |
|||
uni.navigateTo({ |
|||
url: "../../pages/city/CitySelectActivity?city=" + this.page.city + |
|||
"&code=" + this.page.adCode |
|||
}) |
|||
} |
|||
} |
|||
|
|||
if (!this.IsEmpty(val.state)) { |
|||
this.page.state = val.state; |
|||
this.list = [] |
|||
console.log("this.page.state", this.page.state); |
|||
} |
|||
|
|||
this.$refs.mescrollRef.resetPageOne(); |
|||
|
|||
}, |
|||
refresh(page) { |
|||
let _this = this |
|||
this.HTTP({ |
|||
url: 'aos/v1/activityManagement/getActivityAreaPagerList', |
|||
paramsType: "JSON", |
|||
method: "POST", |
|||
data: { |
|||
current: page.num, |
|||
size: 10, |
|||
params: { |
|||
name: "", |
|||
adcode: _this.menuList[0].detailList[0].value, |
|||
orderBy: _this.page.sort, |
|||
sportCategorySid: _this.page.state |
|||
} |
|||
}, |
|||
loading: true |
|||
}).then((res) => { |
|||
// 成功关闭刷新状态 |
|||
_this.$refs.mescrollRef.refreshFinished(res.data.records.length) |
|||
if (page.num == 1) { |
|||
// 第一页 先清空集合数据 |
|||
_this.list = [] |
|||
} |
|||
|
|||
// 追加数据 |
|||
_this.list = _this.list.concat(res.data.records) |
|||
|
|||
}).catch(function(err) { |
|||
console.log("11111"); |
|||
_this.$refs.mescrollRef.refreshError() |
|||
}); |
|||
}, |
|||
|
|||
clickItem(index) { |
|||
|
|||
let raceSid = this.list[index].sid |
|||
console.log(raceSid) |
|||
uni.navigateTo({ |
|||
url: '../index/DetailActivity?raceSid=' + raceSid |
|||
}); |
|||
|
|||
}, |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.top { |
|||
|
|||
display: flex; |
|||
flex-direction: row; |
|||
height: 100rpx; |
|||
background: #FFFFFF; |
|||
opacity: 0.8; |
|||
align-items: center; |
|||
border-radius: 20rpx; |
|||
margin-top: 20rpx; |
|||
margin-left: 30rpx; |
|||
margin-right: 30rpx; |
|||
|
|||
.unselected { |
|||
|
|||
text-align: center; |
|||
font-size: 28rpx; |
|||
font-family: Adobe Heiti Std; |
|||
font-weight: normal; |
|||
color: #191919; |
|||
line-height: 40rpx; |
|||
|
|||
} |
|||
|
|||
.selected { |
|||
text-align: center; |
|||
font-size: 28rpx; |
|||
font-family: Adobe Heiti Std; |
|||
font-weight: normal; |
|||
color: #2CAB69; |
|||
line-height: 40rpx; |
|||
border-bottom: 2rpx #2CAB69 solid; |
|||
padding-bottom: 10rpx; |
|||
} |
|||
|
|||
} |
|||
|
|||
.btn1 { |
|||
background: #BBBBBB; |
|||
} |
|||
|
|||
.btn2 { |
|||
background: #0081D5; |
|||
} |
|||
</style> |
@ -0,0 +1,299 @@ |
|||
<template> |
|||
<view class="content"> |
|||
|
|||
<RefreshView ref="mescrollRef" :pageBg="EDEDED" text="个人中心" :useUpScroll="false" :useDownScroll="false" |
|||
:useTitleLeftBtn="1" titleLeftBtnSource="管理" :dropLeftList="page.btnList" @drop="drop"> |
|||
|
|||
<view class="top"> |
|||
|
|||
<image class="touxiang" :src="page.headImage" mode="aspectFill"></image> |
|||
|
|||
<view class="top-right"> |
|||
<text class="name" @click="bind()">{{page.userNickName}}</text> |
|||
<view style="display: flex;align-items: center;margin-right: 30rpx; margin-top: 35rpx;" |
|||
@click="baseInfo()" v-if="!this.IsEmpty(page.loginName)"> |
|||
<text class="name2" style="flex: 1;">{{page.loginName}}</text> |
|||
<image src="../../static/home-icon/more.png" style="width: 35rpx;height: 35rpx;"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view @click="click(2)" class="menu-item"> |
|||
<image class = "icon" src="../../static/home-icon/game.png" ></image> |
|||
<text class="text">我的活动</text> |
|||
<text class="explain">{{page.participateInActivityExplain}}</text> |
|||
<image class = "more" src="../../static/home-icon/more.png" ></image> |
|||
</view> |
|||
<view class="line-wide"></view> |
|||
<view @click="click(3)" class="menu-item"> |
|||
<image class = "icon" src="../../static/home-icon/game.png" ></image> |
|||
<text class="text">我的队伍</text> |
|||
<image class = "more" src="../../static/home-icon/more.png" ></image> |
|||
</view> |
|||
<view class="line-wide"></view> |
|||
<view @click="click(4)" class="menu-item"> |
|||
<image class = "icon" src="../../static/home-icon/about.png" ></image> |
|||
<text class="text">实名信息</text> |
|||
<image class = "more" src="../../static/home-icon/more.png" ></image> |
|||
</view> |
|||
<view class="line-thin"></view> |
|||
<view @click="click(5)" class="menu-item"> |
|||
<image class = "icon" src="../../static/home-icon/shezhi.png" ></image> |
|||
<text class="text">设置</text> |
|||
<image class = "more" src="../../static/home-icon/more.png" ></image> |
|||
</view> |
|||
</RefreshView> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
page: { |
|||
btnList: [{ |
|||
'name': '报名管理', |
|||
'src': '../../static/game-icon/renyuanguanli.png', |
|||
'id': 0 |
|||
}, { |
|||
'name': '删除活动', |
|||
'src': '../../static/game-icon/shanchu.png', |
|||
'id': 1 |
|||
}, { |
|||
'name': '取消关闭', |
|||
'src': '../../static/game-icon/fanhui.png', |
|||
'id': -1 |
|||
}], |
|||
userNickName: "微信登录/注册", |
|||
headImage: "http://www.ourpyw.com/upload//touxiang/default_tx.jpg", |
|||
isRealAttestation: "", |
|||
participateInActivityExplain: "", |
|||
realAttestationExplain: "认证后可发布活动。未认证", |
|||
loginName: "" |
|||
}, |
|||
|
|||
} |
|||
}, |
|||
methods: { |
|||
|
|||
drop(index, isLeft, selectData) { |
|||
let _this = this; |
|||
|
|||
let id = selectData.id |
|||
|
|||
if (id == 0) { |
|||
// 报名管理 |
|||
uni.navigateTo({ |
|||
url: './ManagerHappyGameMembersActivity?gameSid=' + this.page.gameSid |
|||
}) |
|||
} else if (id == 1) { |
|||
// 删除活动 |
|||
uni.showModal({ |
|||
title: '温馨提示', |
|||
content: '确定删除此赛事?', |
|||
success(res) { |
|||
if (res.confirm) { |
|||
_this.confirm() |
|||
} |
|||
} |
|||
}); |
|||
} else if (id == -1) { |
|||
// 关闭 |
|||
} |
|||
}, |
|||
baseInfo() { |
|||
// 信息中心 |
|||
let _this = this |
|||
this.Login() |
|||
.then((res) => { |
|||
getApp().globalData.memberSid = res |
|||
_this.WritePreference("memberSid", res) |
|||
getApp().globalData.isLogin = true |
|||
// // 真实信息 |
|||
// uni.navigateTo({ |
|||
// url: '../info/RealMessageActivity' |
|||
// }) |
|||
uni.navigateTo({ |
|||
url: '../user/baseInfo' |
|||
}) |
|||
}) |
|||
}, |
|||
click(id) { |
|||
let _this = this |
|||
switch (id) { |
|||
case 1: |
|||
// 信息中心 |
|||
this.Login() |
|||
.then((res) => { |
|||
getApp().globalData.memberSid = res |
|||
_this.WritePreference("memberSid", res) |
|||
getApp().globalData.isLogin = true |
|||
// // 真实信息 |
|||
// uni.navigateTo({ |
|||
// url: '../info/RealMessageActivity' |
|||
// }) |
|||
console.log("asdasdas") |
|||
uni.navigateTo({ |
|||
url: '../user/baseInfo' |
|||
}) |
|||
}) |
|||
|
|||
break; |
|||
case 2: |
|||
// 我的活动 |
|||
this.Login() |
|||
.then((res) => { |
|||
getApp().globalData.memberSid = res |
|||
_this.WritePreference("memberSid", res) |
|||
getApp().globalData.isLogin = true |
|||
uni.navigateTo({ |
|||
url: '../games/MyGamesActivity' |
|||
}) |
|||
|
|||
}) |
|||
// uni.navigateTo({ |
|||
// url: '../web/WebActivity?url=' + this.putWEBExtra("https://www.ourpyw.com/hide/#/") |
|||
// }) |
|||
break; |
|||
case 3: |
|||
// 我的队伍 |
|||
uni.navigateTo({ |
|||
url: "../me/myteam/CreateTeam" |
|||
}) |
|||
case 4: |
|||
uni.navigateTo({ |
|||
url: '../index/UserAuthentication' |
|||
}) |
|||
// // 分享 |
|||
// this.$refs.popup.open() |
|||
break; |
|||
case 5: |
|||
// 设置 |
|||
this.Login() |
|||
.then((res) => { |
|||
getApp().globalData.memberSid = res |
|||
_this.WritePreference("memberSid", res) |
|||
getApp().globalData.isLogin = true |
|||
uni.navigateTo({ |
|||
url: '../setup/setUp' |
|||
}) |
|||
|
|||
}) |
|||
break; |
|||
} |
|||
}, |
|||
bind() { |
|||
if (!this.IsEmpty(getApp().globalData.memberSid)) { |
|||
return; |
|||
} |
|||
// 进入登录页面 |
|||
uni.navigateTo({ |
|||
url: '../me/Login' |
|||
}) |
|||
// 绑定手机号 |
|||
// uni.navigateTo({ |
|||
// url: '../index/BindPhone?sysUserLoginAuthSid=' + |
|||
// getApp().globalData.sysUserLoginAuthSid |
|||
// }) |
|||
}, |
|||
refresh() { |
|||
let _this = this |
|||
if (this.IsEmpty(getApp().globalData.memberSid)) { |
|||
_this.page.userNickName = "微信登录/注册", |
|||
_this.page.headImage = "http://www.ourpyw.com/upload//touxiang/default_tx.jpg", |
|||
_this.page.isRealAttestation = "", |
|||
_this.page.participateInActivityExplain = "", |
|||
_this.page.realAttestationExplain = "认证后可发布活动。未认证", |
|||
_this.page.loginName = "" |
|||
return; |
|||
} |
|||
_this.HTTP({ |
|||
url: 'aos/v1/aosUserManagement/getMyPage/' + getApp().globalData.memberSid, |
|||
method: 'GET', |
|||
data: {}, |
|||
paramsType: "FORM", |
|||
loading: true |
|||
}).then((res) => { |
|||
|
|||
console.log("我的页面初始化", res); |
|||
if (res.code == 200) { |
|||
_this.page = res.data |
|||
_this.page.loginName = "用户号:" + res.data.loginName |
|||
// 成功关闭刷新状态 |
|||
_this.$refs.mescrollRef.refreshFinished() |
|||
|
|||
} |
|||
|
|||
}, (err) => { |
|||
// 失败重置刷新状态 |
|||
_this.$refs.mescrollRef.refreshError() |
|||
|
|||
});; |
|||
|
|||
}, |
|||
|
|||
}, |
|||
onShow() { |
|||
this.refresh() |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import url("../../styles/master.css"); |
|||
.content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100%; |
|||
background-color: #EDEDED; |
|||
box-sizing: border-box; |
|||
|
|||
.top { |
|||
display: flex; |
|||
flex-direction: row; |
|||
padding-bottom: 35rpx; |
|||
padding-top: 55rpx; |
|||
width: 100%; |
|||
background-color: #FFFFFF; |
|||
box-sizing: border-box; |
|||
|
|||
.touxiang { |
|||
width: 150rpx; |
|||
height: 150rpx; |
|||
border-radius: 10%; |
|||
margin-left: 35rpx; |
|||
margin-right: 35rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.top-right { |
|||
display: flex; |
|||
flex-direction: column; |
|||
flex: 1; |
|||
box-sizing: border-box; |
|||
|
|||
.name { |
|||
font-family: sans-serif; |
|||
font-weight: 500; |
|||
color: #101010; |
|||
font-size: 40rpx; |
|||
} |
|||
|
|||
.name2 { |
|||
display: -webkit-box; |
|||
-webkit-line-clamp: 1; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
-webkit-box-orient: vertical; |
|||
word-break: break-all; |
|||
color: #828282; |
|||
font-size: 28rpx; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.user-item-bg { |
|||
background-color: #FFFFFF; |
|||
margin-top: 20rpx; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,312 @@ |
|||
<template> |
|||
<RefreshView ref="mescrollRef" :hasBack="true" :useTitleLeftBtn="0" :useTitleRightBtn="0" |
|||
:text="page.title" :useUpScroll="false"> |
|||
|
|||
<image class="type" src="../../static/dianhua.png" @click="call"></image> |
|||
|
|||
<view class="name">{{data.linkerName}}</view> |
|||
<view class="area">{{data.regionName}}</view> |
|||
<view class="address">{{page.distance}}</view> |
|||
|
|||
<!-- <view class="banner"> |
|||
<swiper class="swiper" indicator-dots="true" autoplay="true"> |
|||
<swiper-item class="swiper-item" v-for="(item,index) in data.listTwo" :key="index">--> |
|||
<image style="width: 100%;height: 450rpx;" :src="data.logo" mode="aspectFill"></image> |
|||
<!-- </swiper-item> |
|||
</swiper> |
|||
</view>--> |
|||
|
|||
<!-- <view style="margin-top: 20rpx;"> |
|||
<text style="background-color: #F1F1F1;border-radius: 5rpx;margin: 15rpx;padding-left: 15rpx; |
|||
padding-right: 15rpx;padding-top: 5rpx;padding-bottom: 5rpx;color: #4c576d;font-size: 22rpx;">{{data.environmentType}}</text> |
|||
</view> |
|||
--> |
|||
<view |
|||
style="display: flex;flex-direction: row;box-sizing: border-box;margin-top: 30rpx;margin-left: 20rpx;margin-right: 20rpx;"> |
|||
|
|||
<text style="font-size: 24rpx;color: #FF5722;">营业时间:</text> |
|||
<text style="font-size: 24rpx;color: #FF5722;">{{data.shopTime}}</text> |
|||
</view> |
|||
|
|||
<view class="nav-bar"> |
|||
<image class="bar-left-img" src="../../static/area_dizhi.png" mode="aspectFit"></image> |
|||
<text class="bar-text">{{data.address}}</text> |
|||
</view> |
|||
|
|||
<view class="line"></view> |
|||
|
|||
<text class="bar-text">球馆简介</text> |
|||
|
|||
<view class="short"> |
|||
<rich-text :nodes="getShort()" class="short"></rich-text> |
|||
</view> |
|||
|
|||
<!-- <uni-popup ref="popupBrowser" type="top"> |
|||
<uni-pop-browser></uni-pop-browser> |
|||
/uni-popup> |
|||
|
|||
#ifdef H5 |
|||
|
|||
<view> |
|||
<drag-button :isDock="true" :existTabBar="true" @btnClick="btnClick" /> |
|||
</view> |
|||
|
|||
#endif |
|||
|
|||
<uni-popup ref="popup" type="share"> |
|||
<uni-popup-share title="分享" @select="select"></uni-popup-share> |
|||
</uni-popup> --> |
|||
|
|||
</RefreshView> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
page: { |
|||
sid: '', |
|||
title: '球馆详情', |
|||
lat: -1, |
|||
lon: -1, |
|||
distance: "" |
|||
}, |
|||
data: {} |
|||
} |
|||
}, |
|||
created() { |
|||
|
|||
let _this = this |
|||
|
|||
// 获取定位 |
|||
uni.getLocation({ |
|||
type: 'gcj02', |
|||
success: function(res) { |
|||
|
|||
let locat = res |
|||
|
|||
_this.page.lat = locat.latitude |
|||
_this.page.lon = locat.longitude |
|||
|
|||
if (_this.page.distance == "") { |
|||
// if (_this.data.point != undefined) { |
|||
// let split = _this.data.point.split(",") |
|||
// // 计算距离 |
|||
// _this.page.distance = "距您" + _this.getDistance(_this.page.lat, _this.page.lon, |
|||
// split[1], split[0]) |
|||
// console.log(_this.page.distance) |
|||
// } |
|||
// 计算距离 |
|||
_this.page.distance = "距您" + _this.getDistance(_this.page.lat, _this.page.lon, |
|||
_this.data.lat, _this.data.lng) |
|||
console.log(_this.page.distance) |
|||
|
|||
} |
|||
|
|||
_this.refresh() |
|||
|
|||
}, |
|||
fail(err) { |
|||
console.log(err) |
|||
} |
|||
}); |
|||
}, |
|||
onLoad(options) { |
|||
this.page.sid = options.sid |
|||
}, |
|||
methods: { |
|||
refresh() { |
|||
|
|||
let _this = this; |
|||
|
|||
this.HTTP({ |
|||
url: 'gms/v1/gymnasiumsManagement/getGymnasiumDetails/'+_this.page.sid, |
|||
paramsType: "FORM", |
|||
method: "GET", |
|||
loading: true |
|||
}).then((res) => { |
|||
|
|||
this.data = res.data |
|||
// 成功关闭刷新状态 |
|||
_this.$refs.mescrollRef.refreshFinished() |
|||
console.log("1》》》", _this.page.distance) |
|||
if (_this.page.distance == "") { |
|||
console.log("2》》》》", _this.page.lat) |
|||
if (_this.page.lat != -1) { |
|||
console.log("point》》》》",_this.data.point) |
|||
// let split = _this.data.point.split(",") |
|||
// // 计算距离 |
|||
// _this.page.distance = "距您" + _this.getDistance(_this.page.lat, _this.page.lon, split[ |
|||
// 1], split[0]) |
|||
// console.log("3》》》》", _this.page.distance) |
|||
|
|||
// 计算距离 |
|||
_this.page.distance = "距您" + _this.getDistance(_this.page.lat, _this.page.lon, |
|||
_this.data.lat, _this.data.lng) |
|||
console.log(_this.page.distance) |
|||
} |
|||
} |
|||
|
|||
}, (err) => { |
|||
// 关闭刷新状态 |
|||
_this.$refs.mescrollRef.refreshFinished() |
|||
}) |
|||
}, |
|||
call() { |
|||
uni.makePhoneCall({ |
|||
phoneNumber: this.data.linkerPhone, |
|||
success(res) { |
|||
console.log(res) |
|||
}, |
|||
fail(err) { |
|||
console.log(err) |
|||
} |
|||
}) |
|||
}, |
|||
getShort() { |
|||
if (this.IsEmpty(this.data.summary)) { |
|||
return "球馆管理员未录入简介" |
|||
} else { |
|||
return this.data.summary |
|||
} |
|||
}, |
|||
daoHang() { |
|||
|
|||
}, |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.short { |
|||
margin-left: 40rpx; |
|||
margin-right: 40rpx; |
|||
|
|||
} |
|||
|
|||
.bar-text { |
|||
font-size: 32rpx; |
|||
height: 100%; |
|||
line-height: 92rpx; |
|||
padding-left: 28rpx; |
|||
} |
|||
|
|||
.name { |
|||
font-size: 33rpx; |
|||
border-radius: 15rpx; |
|||
color: #FFFFFF; |
|||
z-index: 9000; |
|||
top: --window-top; |
|||
margin-top: 300rpx; |
|||
padding: 10rpx 15rpx; |
|||
position: absolute; |
|||
left: 20rpx; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
display: -webkit-box; |
|||
-webkit-line-clamp: 1; |
|||
-webkit-box-orient: vertical; |
|||
line-height: 50rpx; |
|||
} |
|||
|
|||
.area { |
|||
font-size: 28rpx; |
|||
border-radius: 15rpx; |
|||
color: #FFFFFF; |
|||
z-index: 9000; |
|||
top: --window-top; |
|||
margin-top: 350rpx; |
|||
padding: 10rpx 15rpx; |
|||
position: absolute; |
|||
left: 20rpx; |
|||
} |
|||
|
|||
.address { |
|||
font-size: 25rpx; |
|||
border-radius: 15rpx; |
|||
color: #FFFFFF; |
|||
z-index: 9000; |
|||
top: --window-top; |
|||
margin-top: 390rpx; |
|||
padding: 10rpx 15rpx; |
|||
position: absolute; |
|||
left: 20rpx; |
|||
} |
|||
|
|||
.type { |
|||
font-size: 28rpx; |
|||
border-radius: 15rpx; |
|||
color: #2C405A; |
|||
font-weight: bold; |
|||
z-index: 9000; |
|||
top: --window-top; |
|||
margin-top: 410rpx; |
|||
padding: 10rpx 15rpx; |
|||
position: absolute; |
|||
right: 20rpx; |
|||
width: 70rpx; |
|||
height: 70rpx; |
|||
} |
|||
|
|||
|
|||
.banner { |
|||
height: 450rpx; |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
|
|||
.swiper { |
|||
height: 100%; |
|||
width: 100%; |
|||
|
|||
.swiper-item { |
|||
height: 100%; |
|||
width: 100%; |
|||
.banner-image { |
|||
width: 100%; |
|||
height: 100%; |
|||
border-bottom-left-radius: 20rpx; |
|||
border-bottom-right-radius: 20rpx; |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
|||
|
|||
.nav-bar { |
|||
min-height: 92rpx; |
|||
width: 100%; |
|||
display: flex; |
|||
margin-top: 30rpx; |
|||
box-sizing: border-box; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
margin-bottom: 30rpx; |
|||
margin-right: 35rpx; |
|||
|
|||
.bar-left-img { |
|||
width: 60rpx; |
|||
height: 60rpx; |
|||
margin-left: 20rpx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.bar-text { |
|||
font-size: 28rpx; |
|||
flex: 1; |
|||
box-sizing: border-box; |
|||
line-height: 40rpx; |
|||
} |
|||
|
|||
.bar-img { |
|||
width: 90rpx; |
|||
height: 92rpx; |
|||
padding-right: 28rpx; |
|||
} |
|||
} |
|||
|
|||
.line { |
|||
width: 100%; |
|||
height: 18rpx; |
|||
background-color: #f5f4f9; |
|||
} |
|||
</style> |
@ -0,0 +1,136 @@ |
|||
<template> |
|||
<view> |
|||
|
|||
<RefreshView ref="mescrollRef" @refresh="refresh" :hasBack="true" text="报名列表"> |
|||
|
|||
<view v-for="(item,index) in data.list" :key='index' class="list-item-layout"> |
|||
|
|||
<image :src="item.headImage" class="list-item-img" mode="aspectFill"> |
|||
</image> |
|||
|
|||
<view class="list-item-right"> |
|||
<view class="list-item-name">{{item.realName}}</view> |
|||
<view class="list-item-content">报名时间:{{item.timeStr}}</view> |
|||
</view> |
|||
|
|||
</view> |
|||
</RefreshView> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// 必须 |
|||
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js"; |
|||
|
|||
export default { |
|||
// 使用mixin (在main.js注册全局组件) 必须 |
|||
mixins: [MescrollMixin], |
|||
data() { |
|||
return { |
|||
page: { |
|||
activitySid: '', |
|||
activityItemSid: '', |
|||
}, |
|||
data: { |
|||
list: [] |
|||
} |
|||
|
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
// 赋值 |
|||
this.page.activitySid = options.activitySid |
|||
this.page.activityItemSid = options.activityItemSid |
|||
|
|||
}, |
|||
methods: { |
|||
refresh(page) { |
|||
|
|||
let _this = this; |
|||
|
|||
this.HTTP({ |
|||
url: 'aos/v1/participantRelation/getPageListPersonalParticipant', |
|||
paramsType: "JSON", |
|||
method: "POST", |
|||
data: { |
|||
current: page.num, |
|||
size: 10, |
|||
params: { |
|||
activitySid: this.page.activitySid, |
|||
activityItemSid: this.page.activityItemSid , |
|||
} |
|||
}, |
|||
loading: true |
|||
}).then((res) => { |
|||
|
|||
// 成功关闭刷新状态 |
|||
_this.$refs.mescrollRef.refreshFinished(res.data.records.length) |
|||
|
|||
if (page.num == 1) { |
|||
// 第一页 先清空集合数据 |
|||
_this.data.list = [] |
|||
|
|||
} |
|||
|
|||
// 追加数据 |
|||
_this.data.list = _this.data.list.concat(res.data.records) |
|||
|
|||
}).catch(function(err) { |
|||
console.log("11111"); |
|||
_this.$refs.mescrollRef.refreshError() |
|||
}) |
|||
}, |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.list-item-layout { |
|||
display: flex; |
|||
width: 100%; |
|||
flex-direction: row; |
|||
box-sizing: border-box; |
|||
padding-top: 20rpx; |
|||
padding-bottom: 10rpx; |
|||
margin-left: 33rpx; |
|||
margin-right: 33rpx; |
|||
border-bottom: 0.1px #F1F1F1 solid; |
|||
|
|||
.list-item-img { |
|||
width: 140rpx; |
|||
height: 140rpx; |
|||
margin-right: 25rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.list-item-right { |
|||
flex: 1; |
|||
margin-top: 20rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 140rpx; |
|||
|
|||
.list-item-name { |
|||
width: 100%; |
|||
flex: 1; |
|||
color: #333333; |
|||
font-size: 27rpx; |
|||
} |
|||
|
|||
.list-item-content { |
|||
width: 100%; |
|||
flex: 1; |
|||
color: #999999; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.list-item-time { |
|||
width: 100%; |
|||
color: #999999; |
|||
font-size: 24rpx; |
|||
flex: 1; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,146 @@ |
|||
<template> |
|||
<view> |
|||
|
|||
<RefreshView ref="mescrollRef" :hasBack="true" text="绑定手机号" :useDownScroll="false" :useUpScroll="false"> |
|||
|
|||
<view style="margin-top: 30rpx;"> |
|||
<view class="inputRow"> |
|||
<image src="../../static/img/login/username.png" mode="aspectFill" class="drawableLeft"></image> |
|||
<input type="number" maxlength="11" @input="phoneText" placeholder="请输入手机号" class="input" /> |
|||
<SendCodeItem :phoneNum="page.phone" url="aos/v1/aosUser/sendCodeFromWxBindMobile" @click="send" ref="wxCodeItem"></SendCodeItem> |
|||
</view> |
|||
</view> |
|||
|
|||
|
|||
<view class="inputRow"> |
|||
<image src="../../static/img/login/code.png" mode="aspectFill" class="drawableLeft"></image> |
|||
<input type="number" @input="codeText" maxlength="6" placeholder="请输入验证码" class="input" /> |
|||
</view> |
|||
|
|||
<view class="btn" @click="next"> |
|||
<text class="btnText">绑定手机</text> |
|||
</view> |
|||
|
|||
</RefreshView> |
|||
|
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
page: { |
|||
phone: '', |
|||
sysUserWxAuthSid: '', |
|||
code: '' |
|||
} |
|||
}; |
|||
}, |
|||
onLoad(options) { |
|||
this.page.sysUserWxAuthSid = options.sysUserWxAuthSid |
|||
console.log('=======', options) |
|||
console.log('=======', options.sysUserWxAuthSid) |
|||
}, |
|||
methods: { |
|||
next() { |
|||
var phoneLength = this.page.phone.length; |
|||
var codeLength = this.page.code.length; |
|||
if (phoneLength == 0) { |
|||
this.Toast("请输入手机号") |
|||
return; |
|||
} |
|||
if (codeLength == 0) { |
|||
this.Toast("验证码不能为空") |
|||
return; |
|||
} |
|||
let _this = this |
|||
this.HTTP({ |
|||
url: 'aos/v1/aosUser/wxBindMobile', |
|||
data: { |
|||
mobile: this.page.phone, |
|||
sysUserWxAuthSid: this.page.sysUserWxAuthSid, |
|||
code: this.page.code |
|||
}, |
|||
method: 'POST', |
|||
paramsType: "JSON", |
|||
loading: true |
|||
}).then((res) => { |
|||
console.log('=======', res) |
|||
if (res.code == 200) { |
|||
// 保存 |
|||
_this.WritePreference("sysUserSid", res.data) |
|||
_this.WritePreference("isLogin", true) |
|||
getApp().globalData.isLogin = true |
|||
getApp().globalData.sysUserSid = res.data |
|||
console.log('=======1111111111111111111111111111111sdfasdf;kjasdfjkasdklfkasdjf;asdddddddddddddd',res) |
|||
// $emit 触发事件 (主要返回给webviwew页面) |
|||
// uni.$emit('login', res.data.memberSid) |
|||
uni.navigateBack({ |
|||
delta: 10 |
|||
}); |
|||
|
|||
} |
|||
}); |
|||
|
|||
|
|||
}, |
|||
phoneText(e) { |
|||
//手机号 |
|||
this.page.phone = e.detail.value; |
|||
}, |
|||
send(e) { //发送验证码 |
|||
console.log(e); |
|||
}, |
|||
codeText(e) { |
|||
//验证码 |
|||
this.page.code = e.detail.value; |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.inputRow { |
|||
display: flex; |
|||
margin-left: 30rpx; |
|||
margin-right: 30rpx; |
|||
margin-bottom: 10rpx; |
|||
margin-top: 10rpx; |
|||
padding-bottom: 10rpx; |
|||
border-bottom: 0.1px #F1F1F1 solid; |
|||
align-items: center; |
|||
|
|||
.input { |
|||
margin-left: 20rpx; |
|||
height: 70rpx; |
|||
flex: 1; |
|||
font-size: 32rpx; |
|||
} |
|||
|
|||
.drawableLeft { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
margin: 20rpx; |
|||
} |
|||
} |
|||
|
|||
.btn { |
|||
display: flex; |
|||
width: 90%; |
|||
height: 80rpx; |
|||
flex-direction: column; |
|||
background-color: $uni-base-color; |
|||
margin-top: 80rpx; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
align-items: center; |
|||
justify-content: center; |
|||
border-radius: 10rpx; |
|||
|
|||
.btnText { |
|||
color: #ffffff; |
|||
font-size: 33rpx; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,394 @@ |
|||
<template> |
|||
<RefreshView ref="mescrollRef" :hasBack="true" :text="data.name" :useDownScroll="false" :useUpScroll="false" |
|||
useTitleRightBtn="0"> |
|||
|
|||
<view class="banner"> |
|||
<swiper class="swiper" indicator-dots="true" autoplay="true"> |
|||
<swiper-item class="swiper-item" v-for="(item,index) in data.listCoverImage" :key="index"> |
|||
<image style="width: 100%;height: 450rpx;" :src="item" mode="aspectFill"></image> |
|||
</swiper-item> |
|||
</swiper> |
|||
</view> |
|||
|
|||
<view style="display: flex;flex-direction: column;background: #FFFFFF; width: 100%;height: 100%;"> |
|||
|
|||
<!-- <view |
|||
style="display: flex;flex-direction: column;background: #FFFFFF; |
|||
margin-top: 20rpx;margin-left: 30rpx;margin-right: 30rpx; border: 2rpx solid #666666; border-radius: 30rpx;padding: 20px;"> |
|||
--> |
|||
<view> |
|||
<view style="display: flex; flex-direction: row; margin-left: 20rpx;margin-top: 20rpx;"> |
|||
<text |
|||
style="color: #101010; font-size: 36rpx;font-weight: 520; white-space: nowrap;overflow: hidden;text-overflow: ellipsis">{{data.name}}</text> |
|||
<text |
|||
style=" word-break:keep-all; |
|||
white-space:nowrap;display: flex;align-items: center; margin-left: 30rpx; color: #fff; font-size: 20rpx; background-color: #F2BF5C; padding: 3rpx 15rpx;border-radius: 5rpx;">{{data.sportCategoryName}}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view style="background-color: #F1F1F1; height: 5rpx; width: 100%; margin-top: 20rpx;"></view> |
|||
|
|||
<view |
|||
style="display: flex;flex-direction: row;margin-top: 15rpx;align-items: center;margin-left: 20rpx;margin-right: 30rpx;"> |
|||
<text style="font-size: 30rpx;color: #080808;">报名截止:</text> |
|||
<text style="color: #666666;font-size: 26rpx;">{{data.enrollEndTime}}</text> |
|||
<text style="color: #E99D42;font-size: 26rpx;flex: 1;text-align: right;">还有1天</text> |
|||
</view> |
|||
|
|||
<view style="background-color: #F1F1F1; height: 5rpx; width: 100%; margin-top: 20rpx;"></view> |
|||
|
|||
<view style="display: flex;flex-direction: row;margin-top: 15rpx;margin-left: 20rpx;margin-right: 30rpx;"> |
|||
<text style="font-size: 30rpx;color: #080808;">整体活动:</text> |
|||
<view style="display: flex;flex-direction: column;"> |
|||
<text style="color: #666666; font-size: 26rpx;">{{data.startTime}}</text> |
|||
<text style="color: #666666;margin-top: 20rpx;font-size: 26rpx;">{{data.endTime}}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view style=""> |
|||
<view v-for="(item,index) in data.listActivityItemsDetails " :key="index" style="margin-top: 20rpx;"> |
|||
<view |
|||
style="display: flex;flex-direction: row;margin-top: 20rpx;background-color: #F1F1F1;padding: 20rpx 30rpx;"> |
|||
<text style="font-size: 30rpx;flex: 1;">{{item.name}}</text> |
|||
<view style="display: flex;flex-direction: row;"> |
|||
<text style="font-size: 25rpx; color: #E99D42; ">{{item.enrollMoldName}}</text> |
|||
<text |
|||
style="color: #E99D42; margin-left: 10rpx;margin-right: 10rpx; font-size: 25rpx;">/</text> |
|||
<text |
|||
style="color: #E99D42; font-size: 25rpx;">{{item.enrollMoney==0?"免费":item.enrollMoney+'元'}}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view |
|||
style="display: flex;flex-direction: column;margin-top: 20rpx;margin-left: 30rpx;margin-right: 30rpx;"> |
|||
<text style="color: #666666; margin-top: 15rpx;font-size: 28rpx;">{{item.introduction}}</text> |
|||
</view> |
|||
|
|||
<view style="background-color: #F1F1F1; height: 5rpx; width: 100%;margin-top: 15rpx; "> |
|||
</view> |
|||
<!-- <view |
|||
style="display: flex;margin-top: 20rpx;margin-left: 30rpx;margin-right: 30rpx;align-items: center;"> |
|||
<text style="font-size: 30rpx;">活动时间:</text> |
|||
<text |
|||
style="color: #666666; font-size: 25rpx;flex: 1;">{{item.startTime+" 至 "+item.endTime}}</text> |
|||
</view> |
|||
--> |
|||
<view |
|||
style="display: flex;flex-direction: row;margin-top: 20rpx;margin-left: 30rpx;margin-right: 30rpx;align-items: center;"> |
|||
<view style="display: flex;flex-direction: row;align-items: center;flex: 1;"> |
|||
<text style="font-size: 30rpx;color: #E3A428 ;">{{item.startTime}}</text> |
|||
<text |
|||
style="color: #666666; font-size: 30rpx;text-align: right;flex: 1;">{{item.gymnasiumName}}</text> |
|||
</view> |
|||
<image v-if="!showAddress(item.gymnasiumSid)" style="width: 30rpx;height: 30rpx;" |
|||
src="../../static/home-icon/more.png" @click="gymnasiumName(item.gymnasiumSid)"> |
|||
</image> |
|||
</view> |
|||
|
|||
<view style="background-color: #F1F1F1; height: 5rpx; width: 100%;margin-top: 15rpx; "></view> |
|||
|
|||
<view |
|||
style="display: flex;flex-direction: row;margin-top: 20rpx;margin-left: 30rpx;margin-right: 30rpx;align-items: center;"> |
|||
<view style="display: flex;flex-direction: row;align-items: center;flex: 1;"> |
|||
|
|||
<text style="font-size: 30rpx;">报名数</text> |
|||
<text |
|||
style="color: #919191 ; font-size: 25rpx; margin-left: 20rpx;">{{item.enrollNumbers==0?"暂无":item.enrollNumbersLimit+'人'}}</text> |
|||
<text |
|||
style="color: #919191 ; font-size: 25rpx;margin-left: 10rpx;margin-right: 10rpx;">/</text> |
|||
<text |
|||
style="color: #919191 ; font-size: 25rpx;">{{item.enrollNumbersLimit==0?"不限":item.enrollNumbersLimit+'人'}}</text> |
|||
</view> |
|||
|
|||
<view v-if="item.listUserHeadImageUrl.length>0" style="display: flex;flex-direction: row; " |
|||
@click="userList(item.sid)"> |
|||
<view v-for="(url,i) in item.listUserHeadImageUrl " :key="i"> |
|||
<view |
|||
style="display: flex; width: 100%; margin-left: 5rpx; margin-right: 5rpx;justify-content: center;"> |
|||
<image style="border-radius: 50%; width: 50rpx;height: 50rpx;" :src="url" |
|||
mode="aspectFit"></image> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
<image style="width: 30rpx;height: 30rpx;" src="../../static/home-icon/more.png" |
|||
@click="userList(item.sid)"> |
|||
</image> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
|
|||
<view style="margin-top: 20rpx;background-color: #F1F1F1;height: 20rpx;"> |
|||
</view> |
|||
<view style="display: flex;flex-direction: row;margin: 20rpx 30rpx 0rpx 30rpx; align-items: center;"> |
|||
<text style="font-size: 30rpx;">活动介绍</text> |
|||
<view style="background-color: #F1F1F1; height: 10rpx; flex: 1;margin-left: 20rpx; "></view> |
|||
</view> |
|||
<text style="margin: 30rpx; color: #999999;font-size: 28rpx; ">{{data.introduction}}</text> |
|||
|
|||
<view style="display: flex;flex-direction: row;margin: 20rpx 30rpx 0rpx 30rpx; align-items: center;"> |
|||
<text style="font-size: 30rpx;">奖品奖项</text> |
|||
<view style="background-color: #F1F1F1; height: 10rpx; flex: 1;margin-left: 20rpx; "></view> |
|||
</view> |
|||
<text style="margin: 30rpx; color: #999999;font-size: 28rpx; ">{{data.notes}}</text> |
|||
<!-- |
|||
<textarea style="margin: 30rpx; color: #999999 ; font-size: 25rpx;white-space: pre-wrap; " |
|||
read-only="readOnly" disabled="disabled " placeholder="无" v-model="data.notes" /> --> |
|||
|
|||
|
|||
|
|||
<view style="display: flex;flex-direction: row;margin: 10rpx 30rpx 0rpx 30rpx; align-items: center;"> |
|||
<text style="font-size: 30rpx;">特别鸣谢</text> |
|||
<view style="background-color: #F1F1F1; height: 10rpx; flex: 1;margin-left: 20rpx; "></view> |
|||
</view> |
|||
<view |
|||
style="display: flex;margin-left: 40rpx;margin-right: 40rpx;flex-direction: column; margin-top: 30rpx;"> |
|||
<view v-for="(item,pos) in data.listSpecialThanks " :key="pos"> |
|||
<view |
|||
style="display: flex; width: 100%; padding-top: 10rpx;padding-bottom: 10rpx;justify-content: center;"> |
|||
<text style="color: #898989;font: size 28rpx;flex: 1;">{{item.name}}</text> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
<view style="background-color: #F1F1F1; height: 5rpx; width: 100%;margin-top: 20rpx; "></view> |
|||
<view |
|||
style="display: flex;flex-direction: row;margin-top: 20rpx;margin-left: 30rpx; margin-right: 30rpx; align-items: center;"> |
|||
<text style="color: #898989; font-size: 28rpx;">主办方:</text> |
|||
|
|||
<text style="color: #898989;font-size: 25rpx;">{{data.organizer}}</text> |
|||
</view> |
|||
<view style="background-color: #F1F1F1; height: 5rpx; width: 100%; margin-top: 20rpx;"></view> |
|||
<view |
|||
style="display: flex;flex-direction: row;margin-top: 20rpx;margin-left: 30rpx; margin-right: 30rpx; align-items: center;"> |
|||
<text style="color: #898989;font-size: 28rpx;">联系人:</text> |
|||
|
|||
<view style="display: flex;flex-direction: row;"> |
|||
<text style="color: #898989;font-size: 25rpx;">{{data.linkerName}}</text> |
|||
<view style="width: 30rpx;"></view> |
|||
<text style="color: #898989; font-size: 25rpx;">{{data.linkerPhone}}</text> |
|||
</view> |
|||
|
|||
</view> |
|||
<view style="background-color: #F1F1F1; height: 5rpx; width: 100%; margin-top: 20rpx;"></view> |
|||
<view class="agreeMent"> |
|||
<checkbox-group @change="checkboxChange"> |
|||
<checkbox style="transform:scale(0.7)" :checked="checked1"></checkbox> |
|||
</checkbox-group> |
|||
<text class="text2">我已阅读并同意</text> |
|||
<text style="color: #007AFF;">《参赛须知》</text> |
|||
<text style="display: flex;text-align: center; margin-left: 20rpx; padding: 5rpx 10rpx;background: #0081D5; |
|||
color: #FFFFFF; font-size: 28rpx;" @click="click()"> 我要报名 </text> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
<view style="height: 300rpx;"></view> |
|||
|
|||
</RefreshView> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
checked1: true, |
|||
data: { |
|||
|
|||
}, |
|||
raceSid: "" |
|||
|
|||
} |
|||
}, |
|||
onLoad: function(option) { |
|||
this.raceSid = option.raceSid; |
|||
this.HTTP({ |
|||
url: 'aos/v1/activityManagement/getActivityDetails/' + this.raceSid, |
|||
method: 'GET', |
|||
paramsType: "FORM", |
|||
loading: true |
|||
}).then((res) => { |
|||
if (200 == res.code) { |
|||
this.data = res.data |
|||
} else { |
|||
this.Toast(res.msg) |
|||
uni.navigateBack({ |
|||
delta: 1 |
|||
}) |
|||
} |
|||
|
|||
}); |
|||
}, |
|||
|
|||
methods: { |
|||
showAddress(s) { |
|||
console.log("..." + s); |
|||
return this.IsEmpty(s) |
|||
}, |
|||
checkboxChange(e) { |
|||
this.checked1 = !this.checked1 |
|||
console.log(this.checked1) |
|||
}, |
|||
gymnasiumName(e) { |
|||
uni.navigateTo({ |
|||
url: "ArenaDetailActivity?sid=" + e |
|||
}) |
|||
console.log("gymnasiumName>>", e) |
|||
}, |
|||
userList(sid) { |
|||
console.log("zazzz>>", this.data.sid) |
|||
console.log("userList>>", sid) |
|||
uni.navigateTo({ |
|||
url: "BaoMingListActivity?activitySid=" + this.data.sid + "&activityItemSid=" + sid |
|||
}) |
|||
|
|||
}, |
|||
click() { |
|||
let _this = this; |
|||
|
|||
if (!this.checked1) { |
|||
this.Toast("请认真阅读参赛须知,并勾选。") |
|||
return |
|||
} |
|||
|
|||
// if (this.data.listActivityItemsDetails.length == 1) { |
|||
// let evendSid = this.data.sid |
|||
// let eventsSubprojectSid = this.data.listActivityItemsDetails[0].sid |
|||
// this.HTTP({ |
|||
// url: 'aos/events/v1/eventsenroll/save', |
|||
// method: 'POST', |
|||
// data: { |
|||
// 'eventsSid': evendSid, |
|||
// 'eventsSubprojectSid': eventsSubprojectSid, |
|||
// 'participantSid': getApp().globalData.memberSid, |
|||
// 'paymentMemberSid': "", |
|||
// }, |
|||
// paramsType: "JSON", |
|||
// loading: true |
|||
// }).then((res) => { |
|||
|
|||
// if (res.code == 200) { |
|||
// this.Toast("报名成功!") |
|||
// // 返回的页面数,如果 delta 大于现有页面数,则返回到首页。 |
|||
// uni.navigateBack({ |
|||
// delta: 10 |
|||
// }); |
|||
// } else { |
|||
// this.Toast(res.msg) |
|||
// } |
|||
|
|||
|
|||
// }, (err) => { |
|||
// // 错误提示 |
|||
// _this.Toast("出错了:" + err.data.errmsg) |
|||
// }); |
|||
// } else { |
|||
console.log("===========" + this.data.listActivityItemsDetails.length) |
|||
let list = this.data.listActivityItemsDetails |
|||
let newList = [] |
|||
for (var i = 0; i < list.length; i++) { |
|||
newList.push(list[i].name) |
|||
} |
|||
uni.showActionSheet({ |
|||
itemList: newList, |
|||
success: function(res) { |
|||
console.log("itemList==" + newList) |
|||
console.log("res==" + JSON.stringify(res)) |
|||
_this.listSelect(res.tapIndex, newList) |
|||
}, |
|||
fail(e) { |
|||
console.log("reeees==" + JSON.stringify(e)) |
|||
} |
|||
}); |
|||
// } |
|||
|
|||
}, |
|||
listSelect(id, info) { |
|||
console.log(id) |
|||
console.log(info) |
|||
let _this = this |
|||
let evendSid = this.data.sid |
|||
let eventsSubprojectSid = this.data.listActivityItemsDetails[id].sid |
|||
console.log(eventsSubprojectSid) |
|||
this.Login() |
|||
.then((res) => { |
|||
|
|||
this.HTTP({ |
|||
url: 'aos/v1/activityItemManagement/enroll', |
|||
method: 'POST', |
|||
data: { |
|||
'activitySid': evendSid, |
|||
'activityItemsSid': eventsSubprojectSid, |
|||
'participantSid': res, |
|||
'paymentMemberSid': "", |
|||
}, |
|||
paramsType: "JSON", |
|||
loading: true |
|||
}).then((res) => { |
|||
|
|||
if (res.code == 200) { |
|||
this.Toast("报名成功!") |
|||
// 返回的页面数,如果 delta 大于现有页面数,则返回到首页。 |
|||
uni.navigateBack({ |
|||
delta: 10 |
|||
}); |
|||
} else { |
|||
this.Toast(res.msg) |
|||
} |
|||
|
|||
|
|||
}, (err) => { |
|||
// 错误提示 |
|||
_this.Toast("出错了:" + err.data.errmsg) |
|||
}) |
|||
}) |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.items { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.agreeMent { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
margin-top: 50rpx; |
|||
margin-left: 30rpx; |
|||
|
|||
.text2 { |
|||
color: #666666; |
|||
font-size: 30rpx; |
|||
} |
|||
} |
|||
|
|||
|
|||
.banner { |
|||
height: 450rpx; |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
|
|||
.swiper { |
|||
height: 100%; |
|||
width: 100%; |
|||
|
|||
.swiper-item { |
|||
height: 100%; |
|||
width: 100%; |
|||
|
|||
.banner-image { |
|||
width: 100%; |
|||
height: 100%; |
|||
border-bottom-left-radius: 20rpx; |
|||
border-bottom-right-radius: 20rpx; |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,470 @@ |
|||
<template> |
|||
|
|||
<TabLayout ref='tabLayout' text="选择球馆" :tabTitleData="page.tabTitle" @tabClickItem='clickTab' @downRefresh="down"> |
|||
|
|||
<swiper style="min-height: 100vh;" :current="page.currentTab" @change="swiperTab"> |
|||
<swiper-item v-for="(listItem,listIndex) in data.tabList" :key="listIndex" style="box-sizing: border-box;"> |
|||
|
|||
<scroll-view style="height: 100%;" scroll-y="true" @scrolltolower="lower1"> |
|||
|
|||
<view :style="{'width': '100%','padding-top': page.paddingTop}"> |
|||
|
|||
<loading v-if="page.showLoading[listIndex]" :key='listIndex'></loading> |
|||
|
|||
<view class="outer" v-for="(item,index) in listItem"> |
|||
|
|||
<view class="Item"> |
|||
|
|||
<image class="Img" :src="item.logo" mode="aspectFill" @click="img(item.sid)"></image> |
|||
|
|||
<view class="Right" @click="right(item.sid,item.name)"> |
|||
|
|||
<text class="gameTv">{{item.name}}</text> |
|||
<text class="gameTv2">{{item.address}}</text> |
|||
<!-- <text class="gameTv2">{{item.linkerName+" "+item.linkerPhone}}</text> --> |
|||
|
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<loading v-if="page.showBottomLoading[listIndex]" :key="'bottom'+listIndex"></loading> |
|||
|
|||
<noData v-if="page.showNoData[listIndex]" :key="'nodata'+listIndex"></noData> |
|||
|
|||
</view> |
|||
|
|||
</scroll-view> |
|||
</swiper-item> |
|||
|
|||
</swiper> |
|||
|
|||
</TabLayout> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
const util = require('../../util/util.js'); |
|||
export default { |
|||
|
|||
data() { |
|||
return { |
|||
page: { |
|||
tabTitle: ['常驻球馆', '所有球馆'], |
|||
tabType: ["gms/v1/userGymnasiumsManagement/getUserGymnasiumList", |
|||
"gms/v1/gymnasiumsManagement/getGymnasiumAreaVoPagerList" |
|||
], |
|||
currentTab: 0, |
|||
pages: [1, 1], //第几个swiper的第几页 |
|||
nowLoadingPages: [false, false], //是否正在加载 |
|||
paddingTop: '0px', |
|||
showLoading: [false, false], |
|||
showBottomLoading: [false, false], |
|||
showNoData: [false, false], |
|||
city: "石家庄", |
|||
code: "1301" |
|||
}, |
|||
data: { |
|||
tabList: [ |
|||
[], |
|||
[], |
|||
[], |
|||
[] |
|||
] |
|||
}, |
|||
info: { |
|||
address: "", |
|||
placeName: '' |
|||
} |
|||
|
|||
}; |
|||
}, |
|||
onLoad(options) { |
|||
|
|||
let find_city_code = this.ReadPreference("find_city_code") |
|||
let find_city = this.ReadPreference("find_city") |
|||
|
|||
let _this = this |
|||
this.$nextTick() |
|||
.then(function() { |
|||
_this.page.paddingTop = _this.$refs.tabLayout.getViewPagerTop() |
|||
}) |
|||
|
|||
if (this.IsEmpty(find_city_code)) { |
|||
// 首次进入 |
|||
find_city_code = 0; |
|||
|
|||
// 获取定位 |
|||
let _this = this; |
|||
// 获取定位 |
|||
uni.getLocation({ |
|||
type: 'gcj02', |
|||
success: function(res) { |
|||
|
|||
let locat = res |
|||
|
|||
// 先写入定位 |
|||
_this.location = { |
|||
latitude: locat.latitude, |
|||
longitude: locat.longitude |
|||
} |
|||
|
|||
let url = |
|||
"https://restapi.amap.com/v3/geocode/regeo?key=b564c757b4cf4fd4a5d914625ca9373f&location=" + |
|||
res.longitude + |
|||
"," + res.latitude + |
|||
"&poitype=&radius=1000&extensions=all&batch=false&roadlevel=0"; |
|||
|
|||
_this.HttpOtherUrl({ |
|||
url: url |
|||
}).then((res) => { |
|||
|
|||
let json = JSON.stringify(res); |
|||
let info = JSON.parse(json).regeocode.addressComponent; |
|||
// 城市 |
|||
let city = info.city; |
|||
// 城市编码 |
|||
let code = info.adcode; |
|||
|
|||
// 获取到正确的城市编码 |
|||
if (code > 0) { |
|||
|
|||
// 保存本次定位 |
|||
_this.WritePreference("find_city", city) |
|||
_this.WritePreference("find_city_code", code) |
|||
|
|||
_this.page.code = code |
|||
_this.page.city = city |
|||
_this.page.tabTitle[1] = "所有球馆(" + _this.page.city + ")" |
|||
|
|||
} else { |
|||
|
|||
// 未获取到正确的城市编码 |
|||
_this.WritePreference("find_city", "石家庄") |
|||
_this.WritePreference("find_city_code", "1301") |
|||
|
|||
_this.page.code = "1301" |
|||
_this.page.city = "石家庄" |
|||
_this.page.tabTitle[1] = "所有球馆(" + _this.page.city + ")" |
|||
|
|||
} |
|||
}, (err) => { |
|||
|
|||
//获取当前城市失败 |
|||
_this.WritePreference("find_city", "石家庄") |
|||
_this.WritePreference("find_city_code", "1301") |
|||
|
|||
_this.page.code = "1301" |
|||
_this.page.city = "石家庄" |
|||
_this.page.tabTitle[1] = "所有球馆(" + _this.page.city + ")" |
|||
|
|||
}) |
|||
}, |
|||
fail(err) { |
|||
// 定位失败 |
|||
_this.WritePreference("find_city", "石家庄") |
|||
_this.WritePreference("find_city_code", "1301") |
|||
|
|||
_this.page.code = "1301" |
|||
_this.page.city = "石家庄" |
|||
_this.page.tabTitle[1] = "所有球馆(" + _this.page.city + ")" |
|||
} |
|||
}); |
|||
|
|||
} else { |
|||
this.page.code = find_city_code |
|||
this.page.city = find_city |
|||
this.page.tabTitle[1] = "所有球馆(" + this.page.city + ")" |
|||
} |
|||
|
|||
// 加载第一页数据 |
|||
this.down(0) |
|||
}, |
|||
methods: { |
|||
img(sid) { |
|||
uni.navigateTo({ |
|||
url: 'ArenaDetailActivity?sid=' + sid |
|||
}) |
|||
}, |
|||
right(sid, name) { |
|||
this.info.sid = sid |
|||
this.info.name = name |
|||
console.log("传值>" + JSON.stringify(this.info)) |
|||
this.SetResult(this.info) |
|||
}, |
|||
// swiper 滑动 |
|||
swiperTab: function(e) { |
|||
// 模拟tab点击 |
|||
this.$refs.tabLayout.changeTab(e); |
|||
}, |
|||
clickTab(index) { |
|||
this.page.currentTab = index |
|||
// 每次切换时都重新加载页面 |
|||
this.down(index) |
|||
}, |
|||
down(index) { |
|||
// 下拉刷新请求 并更改数据 |
|||
|
|||
let _this = this |
|||
|
|||
this.data.tabList[index] = [] |
|||
//二维数组,开启强制渲染 |
|||
_this.$forceUpdate() |
|||
|
|||
// 初始化当前的页数 |
|||
this.page.pages[index] = 1 |
|||
// 当前页数的加载状态 |
|||
this.page.nowLoadingPages[index] = true |
|||
|
|||
// 只要切换页面就显示 |
|||
this.page.showLoading[index] = true |
|||
this.page.showNoData[index] = false |
|||
|
|||
if (index == 0) { |
|||
this.Login() |
|||
.then((res) => { |
|||
|
|||
let path = index == 0 ? "/" + getApp().globalData.memberSid : ""; |
|||
|
|||
this.HTTP({ |
|||
url: _this.page.tabType[index] + path, |
|||
method: index == 0 ? 'GET' : 'POST', |
|||
paramsType: index == 0 ? 'FORM' : "JSON", |
|||
data: index == 0 ? {} : { |
|||
"current": 1, |
|||
"size": 10, |
|||
"params": { |
|||
"memberSid": getApp().globalData.memberSid, |
|||
"regionId": _this.page.code |
|||
} |
|||
} |
|||
}).then((res) => { |
|||
// 当前页数的加载状态 |
|||
_this.page.nowLoadingPages[index] = false |
|||
// 填充数据 |
|||
_this.data.tabList[index] = res.data |
|||
_this.$refs.tabLayout.downRefresh() |
|||
//二维数组,开启强制渲染 |
|||
_this.$forceUpdate() |
|||
// 关闭loading |
|||
_this.page.showLoading[index] = false |
|||
_this.page.showNoData[index] = res.data.length == 0 |
|||
}, (err) => { |
|||
// 当前页数的加载状态 |
|||
_this.page.nowLoadingPages[index] = false |
|||
_this.$refs.tabLayout.downRefresh() |
|||
//二维数组,开启强制渲染 |
|||
_this.$forceUpdate() |
|||
// 关闭loading |
|||
_this.page.showLoading[index] = false |
|||
_this.page.showNoData[index] = false |
|||
|
|||
}) |
|||
}) |
|||
}else{ |
|||
|
|||
let path = index == 0 ? "/" + getApp().globalData.memberSid : ""; |
|||
|
|||
this.HTTP({ |
|||
url: _this.page.tabType[index] + path, |
|||
method: index == 0 ? 'GET' : 'POST', |
|||
paramsType: index == 0 ? 'FORM' : "JSON", |
|||
data: index == 0 ? {} : { |
|||
"current": 1, |
|||
"size": 10, |
|||
"params": { |
|||
"memberSid": getApp().globalData.memberSid, |
|||
"regionId": _this.page.code |
|||
} |
|||
} |
|||
}).then((res) => { |
|||
// 当前页数的加载状态 |
|||
_this.page.nowLoadingPages[index] = false |
|||
// 填充数据 |
|||
_this.data.tabList[index] = res.data.records |
|||
_this.$refs.tabLayout.downRefresh() |
|||
//二维数组,开启强制渲染 |
|||
_this.$forceUpdate() |
|||
// 关闭loading |
|||
_this.page.showLoading[index] = false |
|||
_this.page.showNoData[index] = res.data.records.length == 0 |
|||
}, (err) => { |
|||
// 当前页数的加载状态 |
|||
_this.page.nowLoadingPages[index] = false |
|||
_this.$refs.tabLayout.downRefresh() |
|||
//二维数组,开启强制渲染 |
|||
_this.$forceUpdate() |
|||
// 关闭loading |
|||
_this.page.showLoading[index] = false |
|||
_this.page.showNoData[index] = false |
|||
|
|||
}) |
|||
} |
|||
|
|||
|
|||
}, |
|||
request(index, pagerStart) { |
|||
|
|||
let _this = this |
|||
|
|||
let path = index == 0 ? "/" + getApp().globalData.memberSid : ""; |
|||
|
|||
this.HTTP({ |
|||
url: _this.page.tabType[index] + path, |
|||
method: index == 0 ? 'GET' : 'POST', |
|||
paramsType: index == 0 ? 'FORM' : "JSON", |
|||
data: index == 0 ? {} : { |
|||
"current": pagerStart, |
|||
"size": 10, |
|||
"params": { |
|||
"memberSid": getApp().globalData.memberSid, |
|||
"regionId": _this.page.code |
|||
} |
|||
|
|||
} |
|||
}).then((res) => { |
|||
|
|||
// 重置加载状态 |
|||
_this.page.nowLoadingPages[index] = false |
|||
_this.$refs.tabLayout.downRefresh() |
|||
_this.page.showBottomLoading[this.page.currentTab] = false |
|||
|
|||
if (res.data.records.length == 0) { |
|||
_this.Toast('没有更多数据了') |
|||
//二维数组,开启强制渲染 |
|||
_this.$forceUpdate() |
|||
return |
|||
} |
|||
|
|||
_this.data.tabList[index] = _this.data.tabList[index].concat(res.data.records) |
|||
//二维数组,开启强制渲染 |
|||
_this.$forceUpdate() |
|||
|
|||
}, (err) => { |
|||
// 重置加载状态 |
|||
_this.page.nowLoadingPages[index] = false |
|||
_this.$refs.tabLayout.downRefresh() |
|||
//二维数组,开启强制渲染 |
|||
_this.$forceUpdate() |
|||
}) |
|||
}, |
|||
// 加载更多 util.throttle为防抖函数 |
|||
lower1: util.throttle(function(e) { |
|||
|
|||
if (this.page.nowLoadingPages[this.page.currentTab]) { |
|||
// 正在加载 拦截请求 |
|||
return |
|||
} |
|||
|
|||
this.page.showBottomLoading[this.page.currentTab] = true |
|||
//二维数组,开启强制渲染 |
|||
this.$forceUpdate() |
|||
|
|||
// 更改请求的页数 |
|||
this.page.pages[this.page.currentTab]++ |
|||
// 正在加载 |
|||
this.page.nowLoadingPages[this.page.currentTab] = true |
|||
|
|||
// 请求数据 |
|||
this.request(this.page.currentTab, this.page.pages[this.page.currentTab]) |
|||
|
|||
}, 300) |
|||
|
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.header { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
margin-left: 30rpx; |
|||
margin-right: 30rpx; |
|||
margin-top: 25rpx; |
|||
padding-bottom: 20rpx; |
|||
|
|||
.textLogin { |
|||
background-color: #E91E63; |
|||
border-radius: 30rpx; |
|||
padding-left: 25rpx; |
|||
padding-right: 25rpx; |
|||
padding-top: 7rpx; |
|||
padding-bottom: 7rpx; |
|||
color: #FFFFFF; |
|||
font-size: 24rpx; |
|||
} |
|||
} |
|||
|
|||
.textGray3 { |
|||
color: #999999; |
|||
font-size: 26rpx; |
|||
margin-top: 5rpx; |
|||
display: block; |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
} |
|||
|
|||
.outer { |
|||
width: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding: 22rpx 38rpx; |
|||
box-sizing: border-box; |
|||
border-bottom: 0.1px #F1F1F1 solid; |
|||
|
|||
.title { |
|||
padding-bottom: 22rpx; |
|||
flex: 1; |
|||
font-size: 28rpx; |
|||
color: #007AFF; |
|||
overflow: hidden; |
|||
box-orient: vertical; |
|||
text-overflow: ellipsis; |
|||
} |
|||
|
|||
.Item { |
|||
display: flex; |
|||
flex-direction: row; |
|||
width: 100%; |
|||
height: 144rpx; |
|||
box-sizing: border-box; |
|||
|
|||
.Img { |
|||
height: 100%; |
|||
width: 220rpx; |
|||
margin-right: 15rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
} |
|||
|
|||
.Right { |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
flex: 1; |
|||
box-sizing: border-box; |
|||
|
|||
.gameTv { |
|||
font-size: 30rpx; |
|||
color: #000000; |
|||
display: -webkit-box; |
|||
-webkit-box-orient: vertical; |
|||
-webkit-line-clamp: 1; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.gameTv2 { |
|||
font-size: 25rpx; |
|||
margin-top: 10rpx; |
|||
color: #666666; |
|||
display: -webkit-box; |
|||
-webkit-box-orient: vertical; |
|||
-webkit-line-clamp: 3; |
|||
overflow: hidden; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,128 @@ |
|||
<template> |
|||
|
|||
<RefreshView ref="mescrollRef" :hasBack="true" text="人数限制" :useDownScroll="false" :useUpScroll="false" |
|||
useTitleRightBtn="1" titleRightBtnSource="保存" @rightBtn='rightBtnClick'> |
|||
|
|||
<view style="display: flex;flex-direction: column;margin-top: 50rpx;margin-left: 30rpx;margin-right: 30rpx;"> |
|||
<text>人数限制</text> |
|||
<view style="display: flex;flex-direction: row;align-items: center;margin-top: 20rpx;"> |
|||
<view style="border: 2px #F1F1F1 solid;display: flex; "> |
|||
<input class="right" type="number" @input="enrollNumbersLimitText" placeholder="请输入人数限制" |
|||
:value="result.enrollNumbersLimit"></input> |
|||
</view> |
|||
<text |
|||
style="display: flex;flex: 1; justify-content: flex-end; font-size: 30rpx;color: #FF0000;">注:0为不限制</text> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<view style="display: flex;flex-direction: column;margin-top: 50rpx;margin-left: 30rpx;margin-right: 30rpx;"> |
|||
<text>报名费用</text> |
|||
<view style="display: flex;flex-direction: row;align-items: center;margin-top: 20rpx;"> |
|||
<view style="border: 2px #F1F1F1 solid;display: flex; "> |
|||
<input class="right" type="digit" @input="enrollMoneyText" placeholder="请输入报名费用" |
|||
:value="result.enrollMoney"></input> |
|||
</view> |
|||
<text |
|||
style="display: flex;flex: 1; justify-content: flex-end; font-size: 30rpx;color: #FF0000;">注:0为免费</text> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
|
|||
<!-- <radio-group v-if="!this.IsEmpty(result.enrollMoney)" style="margin-top: 50rpx; display: flex;flex-direction: row; " @change="radioChange"> |
|||
|
|||
<radio style="display: flex;flex: 1;justify-content: center;" :checked="result.checked1">线上收费</radio> |
|||
<radio style="display: flex;flex: 1;justify-content: center;" :checked="result.checked2">线下收费</radio> |
|||
|
|||
</radio-group> |
|||
--> |
|||
|
|||
</RefreshView> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
result: { |
|||
enrollNumbersLimit: "0", |
|||
enrollMoney: "0", |
|||
// type: "", |
|||
// checked1: "", |
|||
// checked2: "", |
|||
}, |
|||
} |
|||
}, |
|||
onLoad(options) { |
|||
this.result.enrollNumbersLimit = options.enrollNumbersLimit |
|||
this.result.enrollMoney = options.enrollMoney |
|||
// this.result.type = options.type |
|||
// if ("线上收费" == this.result.type) { |
|||
// this.result.checked1 = true |
|||
// this.result.checked2 = false |
|||
// } else { |
|||
// this.result.checked1 = false |
|||
// this.result.checked2 = true |
|||
// } |
|||
console.log("1===" + this.result.index) |
|||
console.log("2===" + this.result.enrollNumbersLimit) |
|||
console.log("3===" + this.result.enrollMoney) |
|||
// console.log("4===" + this.result.type) |
|||
}, |
|||
methods: { |
|||
enrollNumbersLimitText(e) { |
|||
console.log("1===" + e.detail.value) |
|||
this.result.enrollNumbersLimit = e.detail.value |
|||
}, |
|||
enrollMoneyText(e) { |
|||
console.log("2===" + e.detail.value) |
|||
this.result.enrollMoney = e.detail.value |
|||
}, |
|||
// radioChange(e) { |
|||
// this.result.checked1 = !this.result.checked1 |
|||
// this.result.checked2 = !this.result.checked2 |
|||
// console.log('radio发生change事件,携带value值为:', this.result.checked1) |
|||
// console.log('radio发生change事件,携带value值为:', this.result.checked2) |
|||
// if (this.result.checked1) { |
|||
// this.result.type = "线上收费" |
|||
// } else { |
|||
// this.result.type = "线下收费" |
|||
// } |
|||
// console.log('type===》:', this.result.type) |
|||
// }, |
|||
rightBtnClick() { |
|||
|
|||
if (this.IsEmpty(this.result.enrollNumbersLimit)) { |
|||
this.result.enrollNumbersLimit = 0 |
|||
} |
|||
let o = parseInt(this.result.enrollNumbersLimit) |
|||
if (isNaN(o)) { |
|||
this.Toast("输入的格式有误") |
|||
return |
|||
} |
|||
|
|||
if (this.IsEmpty(this.result.enrollMoney)) { |
|||
this.result.enrollMoney = 0 |
|||
} |
|||
let s = parseInt(this.result.enrollMoney) |
|||
if (isNaN(s)) { |
|||
this.Toast("输入的格式有误") |
|||
return |
|||
} |
|||
|
|||
this.SetResult(this.result) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.right { |
|||
padding: 20rpx; |
|||
flex: 1; |
|||
color: #555555; |
|||
font-size: 30rpx; |
|||
} |
|||
</style> |
@ -0,0 +1,127 @@ |
|||
<template> |
|||
<view> |
|||
|
|||
<RefreshView ref="mescrollRef" :hasBack="true" text="用户认证" :useDownScroll="false" :useUpScroll="false"> |
|||
|
|||
<view style="margin-top: 30rpx;"> |
|||
<view class="inputRow"> |
|||
<image src="../../static/login/username.png" mode="aspectFill" class="drawableLeft"></image> |
|||
<input type="number" maxlength="11" @input="phoneText" placeholder="请输入手机号" class="input" /> |
|||
<SendCodeItem :phoneNum="page.phone" url="portal/v1/sysUserManagement/sendCodeFromAttestation" @click="send" ref="wxCodeItem"></SendCodeItem> |
|||
</view> |
|||
</view> |
|||
|
|||
|
|||
<view class="inputRow"> |
|||
<image src="../../static/login/code.png" mode="aspectFill" class="drawableLeft"></image> |
|||
<input type="number" @input="codeText" maxlength="6" placeholder="请输入验证码" class="input" /> |
|||
</view> |
|||
|
|||
<view class="btn" @click="next"> |
|||
<text class="btnText">验证</text> |
|||
</view> |
|||
|
|||
</RefreshView> |
|||
|
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
page: { |
|||
phone: '', |
|||
code: '' |
|||
} |
|||
}; |
|||
}, |
|||
methods: { |
|||
next() { |
|||
var phoneLength = this.page.phone.length; |
|||
var codeLength = this.page.code.length; |
|||
if (phoneLength == 0) { |
|||
this.Toast("请输入手机号") |
|||
return; |
|||
} |
|||
if (codeLength == 0) { |
|||
this.Toast("验证码不能为空") |
|||
return; |
|||
} |
|||
let _this = this |
|||
this.HTTP({ |
|||
url: 'portal/v1/sysUserManagement/verifyCodeFromAttestation', |
|||
data: { |
|||
mobile: this.page.phone, |
|||
code: this.page.code |
|||
}, |
|||
method: 'GET', |
|||
paramsType: "FORM", |
|||
loading: true |
|||
}).then((res) => { |
|||
uni.navigateTo({ |
|||
url: '../info/RealInfo' |
|||
}) |
|||
}); |
|||
|
|||
|
|||
}, |
|||
phoneText(e) { |
|||
//手机号 |
|||
this.page.phone = e.detail.value; |
|||
}, |
|||
send(e) { //发送验证码 |
|||
console.log(e); |
|||
}, |
|||
codeText(e) { |
|||
//验证码 |
|||
this.page.code = e.detail.value; |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.inputRow { |
|||
display: flex; |
|||
margin-left: 30rpx; |
|||
margin-right: 30rpx; |
|||
margin-bottom: 10rpx; |
|||
margin-top: 10rpx; |
|||
padding-bottom: 10rpx; |
|||
border-bottom: 0.1px #F1F1F1 solid; |
|||
align-items: center; |
|||
|
|||
.input { |
|||
margin-left: 20rpx; |
|||
height: 70rpx; |
|||
flex: 1; |
|||
font-size: 32rpx; |
|||
} |
|||
|
|||
.drawableLeft { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
margin: 20rpx; |
|||
} |
|||
} |
|||
|
|||
.btn { |
|||
display: flex; |
|||
width: 90%; |
|||
height: 80rpx; |
|||
flex-direction: column; |
|||
background-color: $uni-base-color; |
|||
margin-top: 80rpx; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
align-items: center; |
|||
justify-content: center; |
|||
border-radius: 10rpx; |
|||
|
|||
.btnText { |
|||
color: #ffffff; |
|||
font-size: 33rpx; |
|||
} |
|||
} |
|||
</style> |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue