191 changed files with 53378 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||
{ |
|||
"presets": ["@babel/preset-env"] |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
NODE_ENV = 'development' |
|||
|
|||
# api请求地址 |
|||
VITE_APP_BASE_URL='http://shop.zeyan.wang/index.php/api/' |
|||
|
|||
# 图片服务器地址 |
|||
VITE_IMG_DOMAIN='http://shop.zeyan.wang/' |
|||
|
|||
# 站点id 仅在编译为小程序时生效 |
|||
VITE_SITE_ID = '100000' |
|||
|
|||
# 本地存储时token的参数名 |
|||
VITE_REQUEST_STORAGE_TOKEN_KEY='wapToken' |
|||
|
|||
# 请求时header中token的参数名 |
|||
VITE_REQUEST_HEADER_TOKEN_KEY='token' |
|||
|
|||
# 请求时header中站点的参数名 |
|||
VITE_REQUEST_HEADER_SITEID_KEY='site-id' |
|||
|
|||
# 请求时header中来源场景的参数名 |
|||
VITE_REQUEST_HEADER_CHANNEL_KEY='channel' |
|||
|
|||
# 应用版本 |
|||
VITE_APP_VERSION='1.0.1' |
|||
@ -0,0 +1,25 @@ |
|||
NODE_ENV = 'production' |
|||
|
|||
# api请求地址 |
|||
VITE_APP_BASE_URL='http://shop.zeyan.wang/index.php/api/' |
|||
|
|||
# 图片服务器地址 |
|||
VITE_IMG_DOMAIN='http://shop.zeyan.wang/' |
|||
|
|||
# 站点id 仅在编译为小程序时生效 |
|||
VITE_SITE_ID = '100000' |
|||
|
|||
# 本地存储时token的参数名 |
|||
VITE_REQUEST_STORAGE_TOKEN_KEY='wapToken' |
|||
|
|||
# 请求时header中token的参数名 |
|||
VITE_REQUEST_HEADER_TOKEN_KEY='token' |
|||
|
|||
# 请求时header中站点的参数名 |
|||
VITE_REQUEST_HEADER_SITEID_KEY='site-id' |
|||
|
|||
# 请求时header中来源场景的参数名 |
|||
VITE_REQUEST_HEADER_CHANNEL_KEY='channel' |
|||
|
|||
# 应用版本 |
|||
VITE_APP_VERSION='1.0.1' |
|||
@ -0,0 +1,22 @@ |
|||
# Logs |
|||
logs |
|||
*.log |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
pnpm-debug.log* |
|||
lerna-debug.log* |
|||
|
|||
node_modules |
|||
.DS_Store |
|||
dist |
|||
*.local |
|||
|
|||
# Editor directories and files |
|||
.idea |
|||
*.suo |
|||
*.ntvs* |
|||
*.njsproj |
|||
*.sln |
|||
*.sw? |
|||
.hbuilderx |
|||
@ -0,0 +1,20 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-cn"> |
|||
<head> |
|||
<meta charset="UTF-8" /> |
|||
<script> |
|||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || |
|||
CSS.supports('top: constant(a)')) |
|||
document.write( |
|||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + |
|||
(coverSupport ? ', viewport-fit=cover' : '') + '" />') |
|||
</script> |
|||
<title></title> |
|||
<!--preload-links--> |
|||
<!--app-context--> |
|||
</head> |
|||
<body> |
|||
<div id="app"><!--app-html--></div> |
|||
<script type="module" src="/src/main.js"></script> |
|||
</body> |
|||
</html> |
|||
File diff suppressed because it is too large
@ -0,0 +1,85 @@ |
|||
{ |
|||
"name": "uni-preset-vue", |
|||
"version": "0.0.0", |
|||
"scripts": { |
|||
"dev:app": "uni -p app", |
|||
"dev:app-android": "uni -p app-android", |
|||
"dev:app-ios": "uni -p app-ios", |
|||
"dev:custom": "uni -p", |
|||
"dev:h5": "uni", |
|||
"dev:h5:ssr": "uni --ssr", |
|||
"dev:mp-alipay": "uni -p mp-alipay", |
|||
"dev:mp-baidu": "uni -p mp-baidu", |
|||
"dev:mp-jd": "uni -p mp-jd", |
|||
"dev:mp-kuaishou": "uni -p mp-kuaishou", |
|||
"dev:mp-lark": "uni -p mp-lark", |
|||
"dev:mp-qq": "uni -p mp-qq", |
|||
"dev:mp-toutiao": "uni -p mp-toutiao", |
|||
"dev:mp-weixin": "uni -p mp-weixin", |
|||
"dev:quickapp-webview": "uni -p quickapp-webview", |
|||
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei", |
|||
"dev:quickapp-webview-union": "uni -p quickapp-webview-union", |
|||
"build:app": "uni build -p app", |
|||
"build:app-android": "uni build -p app-android", |
|||
"build:app-ios": "uni build -p app-ios", |
|||
"build:custom": "uni build -p", |
|||
"build:h5": "uni build && node publish.cjs", |
|||
"build:h5:ssr": "uni build --ssr", |
|||
"build:mp-alipay": "uni build -p mp-alipay", |
|||
"build:mp-baidu": "uni build -p mp-baidu", |
|||
"build:mp-jd": "uni build -p mp-jd", |
|||
"build:mp-kuaishou": "uni build -p mp-kuaishou", |
|||
"build:mp-lark": "uni build -p mp-lark", |
|||
"build:mp-qq": "uni build -p mp-qq", |
|||
"build:mp-toutiao": "uni build -p mp-toutiao", |
|||
"build:mp-weixin": "uni build -p mp-weixin", |
|||
"build:quickapp-webview": "uni build -p quickapp-webview", |
|||
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei", |
|||
"build:quickapp-webview-union": "uni build -p quickapp-webview-union", |
|||
"type-check": "vue-tsc --noEmit" |
|||
}, |
|||
"dependencies": { |
|||
"@dcloudio/uni-app": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-app-plus": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-components": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-h5": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-mp-alipay": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-mp-baidu": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-mp-jd": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-mp-kuaishou": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-mp-lark": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-mp-qq": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-mp-toutiao": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-mp-weixin": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-quickapp-webview": "3.0.0-3080720230703001", |
|||
"html2canvas": "^1.4.1", |
|||
"image-tools": "^1.4.0", |
|||
"lodash-es": "^4.17.21", |
|||
"pinia": "2.0.36", |
|||
"qrcode": "^1.5.1", |
|||
"qs": "6.7.0", |
|||
"sortablejs": "^1.15.0", |
|||
"uview-plus": "^3.1.29", |
|||
"vue": "^3.3.0", |
|||
"vue-i18n": "^9.2.2", |
|||
"weixin-js-sdk": "^1.6.0" |
|||
}, |
|||
"devDependencies": { |
|||
"@dcasia/mini-program-tailwind-webpack-plugin": "^1.5.6", |
|||
"@dcloudio/types": "^3.3.2", |
|||
"@dcloudio/uni-automator": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-cli-shared": "3.0.0-3080720230703001", |
|||
"@dcloudio/uni-stacktracey": "3.0.0-3080720230703001", |
|||
"@dcloudio/vite-plugin-uni": "3.0.0-3080720230703001", |
|||
"@rollup/plugin-commonjs": "^24.0.1", |
|||
"@types/qrcode": "^1.5.0", |
|||
"@types/sortablejs": "^1.15.0", |
|||
"@vue/tsconfig": "^0.1.3", |
|||
"sass": "^1.54.5", |
|||
"typescript": "^4.9.4", |
|||
"vite": "4.0.4", |
|||
"vite-plugin-windicss": "^1.8.10", |
|||
"vue-tsc": "^1.0.24", |
|||
"windicss": "^3.5.6" |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,51 @@ |
|||
const fs = require('fs') |
|||
|
|||
const publish = () => { |
|||
const src = './dist/build/h5' |
|||
const dest = '../niucloud/public/wap' |
|||
|
|||
solve() |
|||
|
|||
// 目标目录不存在停止复制 |
|||
try { |
|||
const dir = fs.readdirSync(dest) |
|||
} catch (e) { |
|||
return |
|||
} |
|||
|
|||
// 删除目标目录下文件 |
|||
fs.rm(dest, { recursive: true }, err => { |
|||
if(err) { |
|||
console.log(err) |
|||
return |
|||
} |
|||
|
|||
fs.cp(src, dest, { recursive: true }, (err) => { |
|||
if (err) { |
|||
console.error(err) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
const solve = () => { |
|||
const src = './dist/build/h5/assets' |
|||
const filemaps = fs.readdirSync(src) |
|||
|
|||
filemaps.forEach(file => { |
|||
if (/^(index-)(\w{8})(.js)$/.test(file)) { |
|||
const path = `${src}/${file}` |
|||
let content = fs.readFileSync(path, 'utf-8') |
|||
const first = 'const match = location.href.match(/\\/wap\\/(\\d*)\\//);' |
|||
|
|||
if (content.indexOf(first) == -1) { |
|||
content = first + content |
|||
const replace = 'router:{mode:"history",base: match ? `/wap/${match[1]}/` : "/wap/",assets:"assets",routerBase: match ? `/wap/${match[1]}/` : "/wap/"},darkmode' |
|||
content = content.replace(/router:{(.*?)},darkmode/s, replace) |
|||
fs.writeFileSync(path, content, 'utf8') |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
publish() |
|||
@ -0,0 +1,111 @@ |
|||
<script setup lang="ts"> |
|||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app' |
|||
import { launchInterceptor } from '@/utils/interceptor' |
|||
import { getToken, isWeixinBrowser, getSiteId } from '@/utils/common' |
|||
import useMemberStore from '@/stores/member' |
|||
import useConfigStore from '@/stores/config' |
|||
import useSystemStore from '@/stores/system' |
|||
import { useLogin } from '@/hooks/useLogin' |
|||
import { useShare } from '@/hooks/useShare' |
|||
|
|||
onLaunch(async(data) => { |
|||
|
|||
// 添加初始化拦截器 |
|||
launchInterceptor() |
|||
|
|||
// #ifdef H5 |
|||
uni.getSystemInfoSync().platform == 'ios' && (uni.setStorageSync('initUrl', location.href)) |
|||
|
|||
// 传输给后台数据 |
|||
window.parent.postMessage(JSON.stringify({ |
|||
type: 'appOnLaunch', |
|||
message: '初始化加载完成' |
|||
}), '*'); |
|||
|
|||
// 监听父页面发来的消息 |
|||
window.addEventListener('message', event => { |
|||
try { |
|||
let data = { |
|||
type: '' |
|||
}; |
|||
if (typeof event.data == 'string') { |
|||
data = JSON.parse(event.data) |
|||
} else if (typeof event.data == 'object') { |
|||
data = event.data |
|||
} |
|||
if (data.type && data.type == 'appOnReady') { |
|||
window.parent.postMessage(JSON.stringify({ |
|||
type: 'appOnReady', |
|||
message: '加载完成' |
|||
}), '*'); |
|||
} |
|||
} catch (e) { |
|||
console.log('uni-app App.vue 接受数据错误', e) |
|||
} |
|||
}, false); |
|||
|
|||
// 缺少站点id,拦截 |
|||
if (process.env.NODE_ENV == 'development' && (getSiteId(uni.getStorageSync('wap_site_id') || import.meta.env.VITE_SITE_ID) === '')) return; |
|||
|
|||
const { wechatInit } = useShare() |
|||
wechatInit() |
|||
// #endif |
|||
|
|||
const configStore = useConfigStore() |
|||
await configStore.getTabbarConfig() |
|||
await configStore.getLoginConfig() |
|||
|
|||
useSystemStore().getMapFn() |
|||
useSystemStore().getSiteInfoFn() |
|||
|
|||
// try { |
|||
// // 隐藏tabbar |
|||
// uni.hideTabBar() |
|||
// } catch (e) { |
|||
|
|||
// } |
|||
|
|||
// 判断是否已登录 |
|||
if (getToken()) { |
|||
const memberStore = useMemberStore() |
|||
await memberStore.setToken(getToken()) |
|||
|
|||
setTimeout(() => { |
|||
if (!uni.getStorageSync('openid')) { |
|||
const memberInfo = useMemberStore().info |
|||
// #ifdef MP-WEIXIN |
|||
memberInfo && memberInfo.weapp_openid && uni.setStorageSync('openid', memberInfo.weapp_openid) |
|||
// #endif |
|||
// #ifdef H5 |
|||
isWeixinBrowser() && memberInfo && memberInfo.wx_openid && uni.setStorageSync('openid', memberInfo.wx_openid) |
|||
// #endif |
|||
} |
|||
}, 1000) |
|||
} |
|||
|
|||
if (!getToken()) { |
|||
const login = useLogin() |
|||
// 第三方平台自动登录 |
|||
// #ifdef MP |
|||
login.getAuthCode() |
|||
// #endif |
|||
// #ifdef H5 |
|||
if (isWeixinBrowser()) { |
|||
data.query.code ? login.authLogin(data.query.code) : login.getAuthCode('snsapi_userinfo') |
|||
} |
|||
// #endif |
|||
} |
|||
}) |
|||
|
|||
onShow(() => { |
|||
}) |
|||
|
|||
onHide(() => { |
|||
}) |
|||
</script> |
|||
|
|||
<style> |
|||
uni-page-head { |
|||
display: none !important; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,97 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
/** |
|||
* 用户名登录 |
|||
*/ |
|||
export function usernameLogin(data : AnyObject) { |
|||
return request.get('login', data, { showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 手机验证码登录 |
|||
*/ |
|||
export function mobileLogin(data : AnyObject) { |
|||
return request.post('login/mobile', data, { showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 获取登录配置 |
|||
*/ |
|||
export function getConfig() { |
|||
return request.get('login/config') |
|||
} |
|||
|
|||
/** |
|||
* 退出登录 |
|||
*/ |
|||
export function logout() { |
|||
return request.put('auth/logout') |
|||
} |
|||
|
|||
/** |
|||
* 用户名注册 |
|||
*/ |
|||
export function usernameRegister(data : AnyObject) { |
|||
let url = 'register' |
|||
if(uni.getStorageSync('pid')){ |
|||
data.pid = uni.getStorageSync('pid'); |
|||
} |
|||
return request.post(url, data, { showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 手机号注册 |
|||
*/ |
|||
export function mobileRegister(data : AnyObject) { |
|||
let url = 'register/mobile' |
|||
if(uni.getStorageSync('pid')){ |
|||
data.pid = uni.getStorageSync('pid'); |
|||
} |
|||
return request.post(url, data, { showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 微信公众号授权信息 |
|||
*/ |
|||
export function wechatUser(data : AnyObject) { |
|||
return request.get('wechat/user', data, { showErrorMessage: false }) |
|||
} |
|||
|
|||
/** |
|||
* 微信公众号授权信息登录(openid) |
|||
*/ |
|||
export function wechatUserLogin(data : AnyObject) { |
|||
return request.post('wechat/userlogin', data, { showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 微信公众号授权登录 |
|||
*/ |
|||
export function wechatLogin(data : AnyObject) { |
|||
return request.post('wechat/login', data, { showErrorMessage: false }) |
|||
} |
|||
|
|||
/** |
|||
* 微信小程序授权登录 |
|||
*/ |
|||
export function weappLogin(data : AnyObject) { |
|||
return request.post('weapp/login', data, { showErrorMessage: false }) |
|||
} |
|||
|
|||
/** |
|||
* 绑定手机号 |
|||
*/ |
|||
export function bind(data : AnyObject) { |
|||
let url = 'bind' |
|||
if(uni.getStorageSync('pid')){ |
|||
data.pid = uni.getStorageSync('pid'); |
|||
} |
|||
return request.post(url, data, { showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 记录会员访问日志 |
|||
*/ |
|||
export function memberLog(data : AnyObject) { |
|||
return request.post('member/log', data, { showErrorMessage: false }) |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
|
|||
/** |
|||
* 绑定设备 |
|||
*/ |
|||
export function set_equipment(params: AnyObject) { |
|||
return request.get(`member/set_equipment`, params) |
|||
} |
|||
/** |
|||
* 档案列表 |
|||
*/ |
|||
export function list_archives() { |
|||
return request.get(`member/list_archives`) |
|||
} |
|||
/** |
|||
* 操作档案 |
|||
*/ |
|||
export function edit_archives(data : any) { |
|||
return request.post(`member/edit_archives`, data) |
|||
} |
|||
/** |
|||
* 删除档案 |
|||
*/ |
|||
export function del_archives(id : number) { |
|||
return request.get(`member/del_archives?id=`+ id) |
|||
} |
|||
/** |
|||
* 设备列表 |
|||
*/ |
|||
export function list_equipment() { |
|||
return request.get(`member/list_equipment`) |
|||
} |
|||
/** |
|||
* 解绑设备 |
|||
*/ |
|||
export function del_equipment(id : number) { |
|||
return request.get(`member/del_equipment?id=`+ id) |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
/** |
|||
* 获取自定义页面信息 |
|||
*/ |
|||
export function getDiyInfo(params: Record<string, any>) { |
|||
return request.get('diy/diy', params) |
|||
} |
|||
|
|||
/** |
|||
* 获取底部导航信息 |
|||
*/ |
|||
export function getTabbarInfo(params: Record<string, any>) { |
|||
return request.get('diy/tabbar', params) |
|||
} |
|||
|
|||
/** |
|||
* 获取底部导航列表 |
|||
*/ |
|||
export function getTabbarList(params: Record<string, any>) { |
|||
return request.get('diy/tabbar/list', params) |
|||
} |
|||
|
|||
/** |
|||
* 获取页面分享信息 |
|||
*/ |
|||
export function getShareInfo(params: Record<string, any>) { |
|||
return request.get('diy/share', params) |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
import request from "@/utils/request"; |
|||
|
|||
// 获取轮播图
|
|||
export function getCarousel() { |
|||
return request.get('/shop/slideshow/list') |
|||
} |
|||
|
|||
// 获取分类
|
|||
export function getClassification() { |
|||
return request.get('/shop/homeCategory/list') |
|||
} |
|||
|
|||
// 获取文章分类
|
|||
export function getArticle(data) { |
|||
return request.get('/cms/category',data) |
|||
} |
|||
|
|||
// 获取医生
|
|||
export function getDoctor(data) { |
|||
return request.get('/shop/doctor/list',data) |
|||
} |
|||
// 获取驿站
|
|||
export function getDak(data) { |
|||
return request.get('/shop/communityStation/list',data) |
|||
} |
|||
|
|||
// 获取科室分类
|
|||
export function getDepartmentClassification(data) { |
|||
return request.get('/shop/doctorsDepartment/list',data) |
|||
} |
|||
|
|||
// 获取驿站详情
|
|||
export function getRelayDetails(data) { |
|||
return request.get('/shop/communityStation/info',data) |
|||
} |
|||
|
|||
// 获取行政区划
|
|||
export function getAdministrativeDivision() { |
|||
return request.get('/accompany/getAreaTree') |
|||
} |
|||
|
|||
// 获取专题列表
|
|||
export function getTopicsList(data) { |
|||
return request.get('/cms/article',data) |
|||
} |
|||
|
|||
// 获取专题详情
|
|||
export function getTopicsDetails(id) { |
|||
return request.get(`/cms/article/${id}`) |
|||
} |
|||
|
|||
// 获取医院分类
|
|||
export function getHospitalClassification() { |
|||
return request.get(`/shop/doctor/getHospitalsAll`) |
|||
} |
|||
|
|||
// 获取医生详情
|
|||
export function getDoctorDetails(data) { |
|||
return request.get(`/shop/goods/detail`,data) |
|||
} |
|||
|
|||
// 商品详情
|
|||
export function getProductDetails(data) { |
|||
return request.get(`/shop/goods/detail`,data) |
|||
} |
|||
|
|||
// 订单计算
|
|||
export function orderCalculation(data) { |
|||
return request.get(`/shop/order_create/calculate`,data) |
|||
} |
|||
|
|||
// 订单创建
|
|||
export function orderCreation(data) { |
|||
return request.post(`/shop/order_create/create`,data) |
|||
} |
|||
|
|||
// 获取通知
|
|||
export function getNotifications(data) { |
|||
return request.get(`/shop/memberNotifications/lists`,data ) |
|||
} |
|||
|
|||
// 获取用户信息
|
|||
export function getUserInfo() { |
|||
return request.get(`/member/member`) |
|||
} |
|||
|
|||
// 获取客服
|
|||
export function customerService() { |
|||
return request.get(`/shop/shopConfig/info`) |
|||
} |
|||
|
|||
|
|||
// 获取支付信息
|
|||
export function getPaymentInfo(trade_type,trade_id) { |
|||
return request.get(`/pay/info/${trade_type}/${trade_id}`) |
|||
} |
|||
|
|||
// 去支付
|
|||
export function toPay(data) { |
|||
return request.post(`/pay`,data) |
|||
} |
|||
@ -0,0 +1,244 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
export function getMemberInfo() { |
|||
return request.get('member/member') |
|||
} |
|||
|
|||
/** |
|||
* 获取积分流水 |
|||
*/ |
|||
export function getPointList(data : AnyObject) { |
|||
return request.get('member/account/point', data) |
|||
} |
|||
|
|||
/** |
|||
* 获取不可提现余额流水 |
|||
*/ |
|||
export function getBalanceList(data : AnyObject) { |
|||
return request.get('member/account/balance', data) |
|||
} |
|||
/** |
|||
* 获取余额流水,条件获取 |
|||
*/ |
|||
export function getBalanceListAll(data : AnyObject) { |
|||
return request.get('member/account/balance_list', data) |
|||
} |
|||
|
|||
/** |
|||
* 获取可提现余额流水 |
|||
*/ |
|||
export function getMoneyList(data : AnyObject) { |
|||
return request.get('member/account/money', data) |
|||
} |
|||
|
|||
/** |
|||
* 会员信息修改 |
|||
*/ |
|||
export function modifyMember(data : AnyObject) { |
|||
return request.put(`member/modify/${data.field}`, data, { showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 登录会员绑定手机号 |
|||
*/ |
|||
export function bindMobile(data : AnyObject) { |
|||
return request.put('member/mobile', data, { showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 提现转账方式 |
|||
*/ |
|||
export function cashOutTransferType() { |
|||
return request.get('member/cash_out/transfertype') |
|||
} |
|||
|
|||
/** |
|||
* 提现配置 |
|||
*/ |
|||
export function cashOutConfig() { |
|||
return request.get('member/cash_out/config') |
|||
} |
|||
|
|||
/** |
|||
* 申请余额提现 |
|||
*/ |
|||
export function cashOutApply(data : AnyObject) { |
|||
return request.post('member/cash_out/apply', data, { showSuccessMessage: true, showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 获取提现账户信息 |
|||
*/ |
|||
export function getCashoutAccountInfo(data : AnyObject) { |
|||
return request.get(`member/cashout_account/${data.account_id}`, {}) |
|||
} |
|||
|
|||
/** |
|||
* 获取首条提现账户信息 |
|||
*/ |
|||
export function getFirstCashoutAccountInfo(data : AnyObject) { |
|||
return request.get('member/cashout_account/firstinfo', data) |
|||
} |
|||
|
|||
/** |
|||
* 获取提现账户列表 |
|||
*/ |
|||
export function getCashoutAccountList(data : AnyObject) { |
|||
return request.get(`member/cashout_account`, data) |
|||
} |
|||
|
|||
/** |
|||
* 获取提现记录列表 |
|||
*/ |
|||
export function getCashOutList(data : AnyObject) { |
|||
return request.get(`member/cash_out`, data) |
|||
} |
|||
|
|||
/** |
|||
* 获取提现记录详情 |
|||
*/ |
|||
export function getCashOutDetail(id : number) { |
|||
return request.get(`member/cash_out/${id}`) |
|||
} |
|||
|
|||
/** |
|||
* 添加提现账户 |
|||
*/ |
|||
export function addCashoutAccount(data : AnyObject) { |
|||
return request.post('member/cashout_account', data, { showSuccessMessage: true, showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 添加提现账户 |
|||
*/ |
|||
export function editCashoutAccount(data : AnyObject) { |
|||
return request.put(`member/cashout_account/${data.account_id}`, data, { showSuccessMessage: true, showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 删除提现账户 |
|||
*/ |
|||
export function deleteCashoutAccount(accountId: number) { |
|||
return request.delete(`member/cashout_account/${accountId}`, { showSuccessMessage: true, showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 佣金账户流水 |
|||
*/ |
|||
export function getMemberCommission(data : AnyObject) { |
|||
return request.get(`member/account/commission`,data) |
|||
} |
|||
/** |
|||
* 佣金列表 |
|||
*/ |
|||
export function getCommissionList(data : AnyObject) { |
|||
return request.get(`member/account/commission`, data) |
|||
} |
|||
|
|||
/** |
|||
* 获取账号变动类型 |
|||
*/ |
|||
export function getAccountType(params: Record<string, any>) { |
|||
return request.get(`member/account/fromtype/${params.account_type}`) |
|||
} |
|||
|
|||
/** |
|||
* 获取会员收货地址列表 |
|||
* @param params |
|||
* @returns |
|||
*/ |
|||
export function getAddressList(params: Record<string, any>) { |
|||
return request.get(`member/address`, params) |
|||
} |
|||
|
|||
/** |
|||
* 获取会员收货地址详情 |
|||
* @param id 会员收货地址id |
|||
* @returns |
|||
*/ |
|||
export function getAddressInfo(id: number) { |
|||
return request.get(`member/address/${id}`); |
|||
} |
|||
|
|||
/** |
|||
* 添加会员收货地址 |
|||
* @param params |
|||
* @returns |
|||
*/ |
|||
export function addAddress(params: Record<string, any>) { |
|||
return request.post('member/address', params, { showErrorMessage: true, showSuccessMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 编辑会员收货地址 |
|||
* @param params |
|||
* @returns |
|||
*/ |
|||
export function editAddress(params: Record<string, any>) { |
|||
return request.put(`member/address/${params.id}`, params, { showErrorMessage: true, showSuccessMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 删除会员收货地址 |
|||
* @param id |
|||
* @returns |
|||
*/ |
|||
export function deleteAddress(id: number) { |
|||
return request.delete(`member/address/${id}`, { showErrorMessage: true, showSuccessMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 获取会员等级 |
|||
*/ |
|||
export function getMemberLevel() { |
|||
return request.get(`member/level`); |
|||
} |
|||
|
|||
/** |
|||
* 获取成长值任务 |
|||
*/ |
|||
export function getTaskGrowth() { |
|||
return request.get(`task/growth`); |
|||
} |
|||
|
|||
/** |
|||
* 获取签到日期 |
|||
*/ |
|||
export function getSignInfo(data : AnyObject) { |
|||
return request.get(`member/sign/info/${data.year}/${data.month}`, {}) |
|||
} |
|||
|
|||
/** |
|||
* 获取日签到奖励 |
|||
*/ |
|||
export function getDayPack(data : AnyObject) { |
|||
return request.get(`member/sign/award/${data.year}/${data.month}/${data.day}`) |
|||
} |
|||
/** |
|||
* 获取签到设置 |
|||
*/ |
|||
export function getSignConfig() { |
|||
return request.get(`member/sign/config`) |
|||
} |
|||
|
|||
/** |
|||
* 点击签到 |
|||
* @returns |
|||
*/ |
|||
export function setSign() { |
|||
return request.post('member/sign') |
|||
} |
|||
|
|||
/** |
|||
* 获取个人积分 |
|||
*/ |
|||
export function getMemberAccountPointcount() { |
|||
return request.get(`member/account/pointcount`) |
|||
} |
|||
|
|||
/** |
|||
* 获取积分任务 |
|||
*/ |
|||
export function getTaskPoint() { |
|||
return request.get(`task/point`) |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
/** |
|||
* 个人中心 |
|||
*/ |
|||
export function getUserDet() { |
|||
return request.get('member/member') |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
/** |
|||
* 支付 |
|||
*/ |
|||
export function pay(data : AnyObject) { |
|||
return request.post(`pay`, data, { showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 获取支付信息 |
|||
*/ |
|||
export function getPayInfo(tradeType : string, tradeId : number) { |
|||
return request.get(`pay/info/${tradeType}/${tradeId}`, {}, { showErrorMessage: true }) |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
/** |
|||
* 商品分类树状结构 |
|||
*/ |
|||
export function goodsTree() { |
|||
return request.get(`shop/goods/category/tree`) |
|||
} |
|||
/** |
|||
* 商品列表 |
|||
*/ |
|||
export function goodsList(params : Record<string, any>) { |
|||
return request.get(`shop/goods/pages`, params) |
|||
} |
|||
/** |
|||
* 获取商品详情 |
|||
*/ |
|||
export function goodsDetail(params : Record<string, any>) { |
|||
return request.get(`shop/goods/detail`, params) |
|||
} |
|||
/** |
|||
* 商品收藏-添加 |
|||
*/ |
|||
export function goodsCollect(id : number) { |
|||
return request.post(`shop/goods/collect/`+id) |
|||
} |
|||
/** |
|||
* 购物车-添加 |
|||
*/ |
|||
export function shopCart(data : any) { |
|||
return request.post(`shop/cart`, data) |
|||
} |
|||
/** |
|||
* 购物车-列表 |
|||
*/ |
|||
export function shopcartList() { |
|||
return request.get(`shop/cart`) |
|||
} |
|||
/** |
|||
* 购物车-删除 |
|||
*/ |
|||
export function deleteCart(data : any) { |
|||
return request.put(`shop/cart/delete`, data) |
|||
} |
|||
/** |
|||
* 获取商品分类列表 |
|||
*/ |
|||
export function categoryList(params : Record<string, any>) { |
|||
return request.get(`shop/goods/category/list`, params) |
|||
} |
|||
/** |
|||
* 订单-计算(创建订单第1步) |
|||
*/ |
|||
export function calculateCreate(params : Record<string, any>) { |
|||
return request.get(`shop/order_create/calculate`, params) |
|||
} |
|||
/** |
|||
* 订单-创建(创建订单第2步-完成订单的创建) |
|||
*/ |
|||
export function orderCreate(data : Record<string, any>) { |
|||
return request.post(`shop/order_create/create`, data) |
|||
} |
|||
|
|||
/** |
|||
* 获取支付信息 |
|||
*/ |
|||
export function getPayInfo(tradeType : string, tradeId : number) { |
|||
return request.get(`pay/info/${tradeType}/${tradeId}`, {}, { showErrorMessage: true }) |
|||
} |
|||
/** |
|||
* 去支付(第2步) |
|||
|
|||
*/ |
|||
export function goPayy(data : Record<string, any>) { |
|||
return request.post(`pay`,data) |
|||
} |
|||
@ -0,0 +1,147 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
/** |
|||
* 获取验证码 |
|||
*/ |
|||
export function getCaptcha() { |
|||
return request.get('captcha', {}, {showErrorMessage: true}) |
|||
} |
|||
|
|||
/** |
|||
* 获取微信公众号授权码 |
|||
*/ |
|||
export function getWechatAuthCode(data: AnyObject) { |
|||
return request.get('wechat/codeurl', data, {showErrorMessage: false}) |
|||
} |
|||
|
|||
/** |
|||
* 同步微信信息 |
|||
*/ |
|||
export function wechatSync(data: AnyObject) { |
|||
return request.post('wechat/sync', data, {showErrorMessage: false}) |
|||
} |
|||
|
|||
/** |
|||
* 获取协议信息 |
|||
*/ |
|||
export function getAgreementInfo(key: string) { |
|||
return request.get(`agreement/${key}`) |
|||
} |
|||
|
|||
/** |
|||
* 重置密码 |
|||
*/ |
|||
export function resetPassword(data: AnyObject) { |
|||
return request.post(`password/reset`, data, {showErrorMessage: true}) |
|||
} |
|||
|
|||
/** |
|||
* 发送短信验证码 |
|||
*/ |
|||
export function sendSms(data: AnyObject) { |
|||
return request.post(`send/mobile/${data.type}`, data, {showErrorMessage: true}) |
|||
} |
|||
|
|||
/** |
|||
* 获取微信jssdk config |
|||
*/ |
|||
export function getWechatSkdConfig(data: AnyObject) { |
|||
return request.get('wechat/jssdkconfig', data, {showErrorMessage: false}) |
|||
} |
|||
|
|||
/** |
|||
* 上传图片 |
|||
*/ |
|||
export function uploadImage(data: AnyObject) { |
|||
return request.upload('file/image', data, {showErrorMessage: true}) |
|||
} |
|||
|
|||
/** |
|||
* 拉取图片 |
|||
*/ |
|||
export function fetchImage(data: AnyObject) { |
|||
return request.post('file/image/fetch', data) |
|||
} |
|||
|
|||
/** |
|||
* 拉取base64图片 |
|||
*/ |
|||
export function fetchBase64Image(data: AnyObject) { |
|||
return request.post('file/image/base64', data) |
|||
} |
|||
|
|||
/** |
|||
* 获取站点信息 |
|||
*/ |
|||
export function getSiteInfo() { |
|||
return request.get('site') |
|||
} |
|||
|
|||
/** |
|||
* 获取微信小程序订阅消息模板id |
|||
*/ |
|||
export function getWeappTemplateId(keys: string) { |
|||
return request.get('weapp/subscribemsg', {keys}) |
|||
} |
|||
|
|||
/** |
|||
* 获取下级地址列表 |
|||
* @param pid |
|||
*/ |
|||
export function getAreaListByPid(pid: number = 0) { |
|||
return request.get(`area/list_by_pid/${pid}`) |
|||
} |
|||
|
|||
/** |
|||
* 获取地址树列表 |
|||
* @param level |
|||
*/ |
|||
export function getAreatree(level: number = 1) { |
|||
return request.get(`area/tree/${level}`) |
|||
} |
|||
|
|||
/** |
|||
* 获取地址 |
|||
* @param code |
|||
*/ |
|||
export function getAreaByCode(code: number | string) { |
|||
return request.get(`area/code/${code}`) |
|||
} |
|||
|
|||
/** |
|||
* 通过经纬度查询地址 |
|||
* @param params |
|||
*/ |
|||
export function getAddressByLatlng(params: Record<string, any>) { |
|||
return request.get(`area/address_by_latlng`, params, {showErrorMessage: true}) |
|||
} |
|||
|
|||
/** |
|||
* 获取手机端首页列表 |
|||
*/ |
|||
export function getWapIndexList(data: AnyObject) { |
|||
return request.get('wap_index', data) |
|||
} |
|||
|
|||
/** |
|||
* 获取海报 |
|||
* @returns |
|||
*/ |
|||
export function getPoster(params: Record<string, any>) { |
|||
return request.get("poster", params) |
|||
} |
|||
|
|||
/** |
|||
* 获取地图设置 |
|||
*/ |
|||
export function getMap() { |
|||
return request.get('map') |
|||
} |
|||
|
|||
/** |
|||
* 通过外部交易号获取消息跳转路径 |
|||
* @param params |
|||
*/ |
|||
export function getMsgJumpPath(params: Record<string, any>) { |
|||
return request.get('weapp/getMsgJumpPath', params) |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
/** |
|||
* 获取核销信息 |
|||
*/ |
|||
export function getVerifyCode(type: string ,params: AnyObject) { |
|||
return request.get('verify', {type, data: params}) |
|||
} |
|||
|
|||
/** |
|||
* 获取核销记录 |
|||
*/ |
|||
export function getVerifyRecords(params: Record<string, any>) { |
|||
return request.get('verify_records', params) |
|||
} |
|||
|
|||
/** |
|||
* 判断是否是核销员 |
|||
*/ |
|||
export function getCheckVerifier() { |
|||
return request.get('check_verifier') |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 获取核销信息 |
|||
*/ |
|||
export function getVerifierInfo(code: string) { |
|||
return request.get(`get_verify_by_code/${code}`) |
|||
} |
|||
|
|||
/** |
|||
* 核销 |
|||
*/ |
|||
export function verify(code: string) { |
|||
return request.post(`verify/${code}`,{}, { showSuccessMessage: true, showErrorMessage: true }) |
|||
} |
|||
|
|||
/** |
|||
* 获取核销详情 |
|||
*/ |
|||
export function getVerifyDetail(code: string) { |
|||
return request.get(`verify_detail/${code}`,{}, { showErrorMessage: true }) |
|||
} |
|||
|
|||
|
|||
|
|||
@ -0,0 +1,219 @@ |
|||
<template> |
|||
<view :style="warpCss"> |
|||
<view :style="maskLayer"></view> |
|||
<view class="diy-active-cube relative"> |
|||
<view class="active-cube-wrap p-[20rpx]"> |
|||
<view class="flex items-center" v-if="diyComponent.titleStyle.value == 'style-1'"> |
|||
<view class="mr-[20rpx] font-bold text-[32rpx]" :style="{color: diyComponent.titleColor }" @click="diyStore.toRedirect(diyComponent.textLink)">{{ diyComponent.text }}</view> |
|||
<view v-if="diyComponent.subTitle.text" @click="diyStore.toRedirect(diyComponent.subTitle.link)" class="text-center text-[24rpx] rounded-[40rpx] rounded-tl-none py-[10rpx] px-[20rpx]" :style="{'color': diyComponent.subTitle.textColor, background: 'linear-gradient(90deg, '+ diyComponent.subTitle.startColor + ', '+ diyComponent.subTitle.endColor + ')'}">{{ diyComponent.subTitle.text }}</view> |
|||
</view> |
|||
<view class="flex items-center" v-if="diyComponent.titleStyle.value == 'style-2'"> |
|||
<view class="mr-[20rpx] font-bold text-[32rpx]" :style="{color: diyComponent.titleColor }" @click="diyStore.toRedirect(diyComponent.textLink)">{{ diyComponent.text }}</view> |
|||
<view v-if="diyComponent.subTitle.text" @click="diyStore.toRedirect(diyComponent.subTitle.link)" class="text-center text-[24rpx] rounded-[10rpx] py-[10rpx] px-[20rpx]" :style="{'color': diyComponent.subTitle.textColor, background: 'linear-gradient(90deg, '+ diyComponent.subTitle.startColor + ', '+ diyComponent.subTitle.endColor + ')'}">{{ diyComponent.subTitle.text }}</view> |
|||
</view> |
|||
<view class="flex items-center" v-if="diyComponent.titleStyle.value == 'style-3'"> |
|||
<view class="mr-[20rpx] font-bold text-[32rpx]" @click="diyStore.toRedirect(diyComponent.textLink)" :style="{color: diyComponent.titleColor }">{{ diyComponent.text }}</view> |
|||
<view class="relative h-[44rpx]" @click="diyStore.toRedirect(diyComponent.subTitle.link)"> |
|||
<view v-if="diyComponent.subTitle.text" class="text-center text-[24rpx] py-[10rpx] pl-[16rpx] pr-[36rpx]" :style="{'color': diyComponent.subTitle.textColor, background: 'linear-gradient(90deg, '+ diyComponent.subTitle.startColor + ', '+ diyComponent.subTitle.endColor + ')'}">{{ diyComponent.subTitle.text }}</view> |
|||
<image class="absolute left-0 top-0 bottom-0 !w-[16rpx] !h-[44rpx]" :src="img('static/resource/images/diy/active_cube/block_style2_1.png')" mode="scaleToFill"/> |
|||
<image class="absolute right-0 top-0 bottom-0 !w-[28rpx] !h-[44rpx]" :src="img('static/resource/images/diy/active_cube/block_style2_2.png')" mode="scaleToFill"/> |
|||
</view> |
|||
</view> |
|||
<view class="flex items-center justify-between" v-if="diyComponent.titleStyle.value == 'style-4'"> |
|||
<view class="font-bold text-[32rpx]" @click="diyStore.toRedirect(diyComponent.textLink)" :style="{color: diyComponent.titleColor }">{{ diyComponent.text }}</view> |
|||
<view v-if="diyComponent.subTitle.text" @click="diyStore.toRedirect(diyComponent.subTitle.link)" class="text-[24rpx] rounded-[40rpx] py-[10rpx] pl-[16rpx] pr-[12rpx] flex items-center" :style="{'color': diyComponent.subTitle.textColor, background: 'linear-gradient(90deg, '+ diyComponent.subTitle.startColor + ', '+ diyComponent.subTitle.endColor + ')'}"> |
|||
<text>{{ diyComponent.subTitle.text }}</text> |
|||
<text class="nc-iconfont nc-icon-youV6xx !text-[26rpx]"></text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="bd flex flex-wrap justify-between"> |
|||
<template v-for="item in diyComponent.list" :key="item.id"> |
|||
<view v-if="diyComponent.blockStyle.value == 'style-1'" @click="diyStore.toRedirect(item.link)" class="item flex justify-between p-[20rpx] bg-white mt-[20rpx] rounded-[16rpx]" :style="{ backgroundColor : diyComponent.elementBgColor }"> |
|||
<view class="flex-1 flex items-baseline flex-col"> |
|||
<view class="text--[28rpx] pb-[20rpx]" :style="{ fontWeight : diyComponent.blockStyle.fontWeight }">{{ item.title.text }}</view> |
|||
<view class="text--[24rpx] text-gray-500 pb-[20rpx]">{{ item.subTitle.text }}</view> |
|||
<view class="link relative text-[24rpx] leading-[40rpx] flex items-center text-white rounded-r-[20rpx] h-[40rpx] pl-[26rpx] pr-[10rpx]" :style="btnCss(item.moreTitle)" v-if="item.moreTitle.text"> |
|||
<text class="mr-[8rpx]">{{ item.moreTitle.text }}</text> |
|||
<text class="iconfont iconjiantou-you-cuxiantiao-fill !text-[20rpx] text-[#fff]"></text> |
|||
<image class="absolute left-0 top-0 bottom-0 !w-[28rpx]" :src="img('static/resource/images/diy/active_cube/block_style1_1.png')" mode="scaleToFill"/> |
|||
</view> |
|||
</view> |
|||
<view class="img-box ml-[10rpx] w-[130rpx]" v-if="item.imageUrl"> |
|||
<image :src="img(item.imageUrl)" mode="aspectFit" /> |
|||
</view> |
|||
<view class="img-box ml-[10rpx] flex items-center justify-center w-[130rpx] bg-[#f3f4f6]" v-else> |
|||
<u-icon name="photo" color="#999" size="50"></u-icon> |
|||
</view> |
|||
</view> |
|||
<view v-if="diyComponent.blockStyle.value == 'style-2'" @click="diyStore.toRedirect(item.link)" class="item flex justify-between p-[20rpx] bg-white mt-[20rpx] rounded-[16rpx]" :style="{ backgroundColor : diyComponent.elementBgColor }"> |
|||
<view class="flex-1 flex items-baseline flex-col"> |
|||
<view class="text--[28rpx] pb-[20rpx]" :style="{ fontWeight : diyComponent.blockStyle.fontWeight }">{{ item.title.text }}</view> |
|||
<view class="text--[24rpx] text-gray-500 pb-[20rpx]">{{ item.subTitle.text }}</view> |
|||
<view class="link relative text-[24rpx] leading-[40rpx] flex items-center text-white rounded-[20rpx] h-[40rpx] pl-[20rpx] pr-[10rpx]" :style="btnCss(item.moreTitle)" v-if="item.moreTitle.text"> |
|||
<text class="mr-[8rpx]">{{ item.moreTitle.text }}</text> |
|||
<text class="iconfont iconjiantou-you-cuxiantiao-fill !text-[20rpx] text-[#fff]"></text> |
|||
</view> |
|||
</view> |
|||
<view class="img-box ml-[10rpx] w-[130rpx]" v-if="item.imageUrl"> |
|||
<image :src="img(item.imageUrl)" mode="aspectFit" /> |
|||
</view> |
|||
<view class="img-box ml-[10rpx] flex items-center justify-center w-[130rpx] bg-[#f3f4f6]" v-else> |
|||
<u-icon name="photo" color="#999" size="50"></u-icon> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
</view> |
|||
|
|||
<scroll-view scroll-x="true" class="whitespace-nowrap" v-if="diyComponent.blockStyle.value == 'style-3'"> |
|||
<view v-for="(item,index) in diyComponent.list" :key="item.id" class="inline-flex"> |
|||
<view @click="diyStore.toRedirect(item.link)" class="flex flex-col items-center justify-between p-[10rpx] bg-white mt-[20rpx] w-[157rpx] h-[200rpx] rounded-[10rpx] box-border" :class="{'mr-[14rpx]': (index+1 != diyComponent.list.length)}"> |
|||
<view class="w-[141rpx] h-[141rpx]" v-if="item.imageUrl"> |
|||
<image class="w-[141rpx] h-[141rpx]" :src="img(item.imageUrl)" mode="aspectFit" /> |
|||
</view> |
|||
<view class="w-[141rpx] h-[141rpx] relative flex-shrink-0" v-else> |
|||
<view class="absolute left-0 top-0 flex items-center justify-center w-[141rpx] h-[141rpx] bg-[#f3f4f6]"> |
|||
<u-icon name="photo" color="#999" size="50"></u-icon> |
|||
</view> |
|||
</view> |
|||
<view class="my-[10rpx] text-[26rpx]" :style="{ color : item.title.textColor,fontWeight : diyComponent.blockStyle.fontWeight }">{{ item.title.text }}</view> |
|||
</view> |
|||
</view> |
|||
</scroll-view> |
|||
|
|||
<scroll-view scroll-x="true" class="whitespace-nowrap" v-if="diyComponent.blockStyle.value == 'style-4'"> |
|||
<view v-for="(item,index) in diyComponent.list" :key="item.id" class="inline-flex"> |
|||
<view @click="diyStore.toRedirect(item.link)" class="flex flex-col items-center justify-between p-[4rpx] bg-[#F93D02] mt-[20rpx] rounded-[20rpx] box-border" :class="{'mr-[14rpx]': index+1 != diyComponent.list.length}" :style="{ background : 'linear-gradient('+ item.listFrame.startColor +','+ item.listFrame.endColor + ')' }"> |
|||
<view class="w-[149rpx] h-[149rpx] box-border px-[18rpx] pt-[16rpx] pb-[6rpx] bg-[#fff] flex flex-col items-center rounded-[16rpx]"> |
|||
<view class="w-[112rpx] h-[102rpx]" v-if="item.imageUrl"> |
|||
<image class="w-[112rpx] h-[102rpx]" :src="img(item.imageUrl)" mode="aspectFit" /> |
|||
</view> |
|||
<view class="w-[112rpx] h-[102rpx] relative flex-shrink-0" v-else> |
|||
<view class="absolute left-0 top-0 flex items-center justify-center w-[112rpx] h-[102rpx] bg-[#f3f4f6]"> |
|||
<u-icon name="photo" color="#999" size="50"></u-icon> |
|||
</view> |
|||
</view> |
|||
<view class="relative -mt-[10rpx] text-[22rpx] bg-[#F3DAC5] text-[#ED6E00] rounded-[16rpx] px-[12rpx] leading-[36rpx]" :style="{ color : item.subTitle.textColor, background : 'linear-gradient(to right,'+ item.subTitle.startColor +','+ item.subTitle.endColor + ')' }">{{ item.subTitle.text }}</view> |
|||
</view> |
|||
<view class="mt-[10rpx] mb-[6rpx] text-[28rpx] text-[#fff]" :style="{ fontWeight : diyComponent.blockStyle.fontWeight }">{{ item.title.text }}</view> |
|||
</view> |
|||
</view> |
|||
</scroll-view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// 活动魔方组件 |
|||
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
import { img } from '@/utils/common'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
|
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
style += 'position:relative;'; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
|
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`; |
|||
style += 'background-size: cover;background-repeat: no-repeat;'; |
|||
} |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
|
|||
return style; |
|||
|
|||
}) |
|||
|
|||
// 背景图加遮罩层 |
|||
const maskLayer = computed(()=>{ |
|||
var style = ''; |
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += 'position:absolute;top:0;width:100%;'; |
|||
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`; |
|||
style += `height:${height.value}px;`; |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
} |
|||
|
|||
return style; |
|||
}); |
|||
|
|||
const btnCss = (item:any) => { |
|||
var style = ''; |
|||
style += `background:linear-gradient(90deg,${item.startColor},${item.endColor});`; |
|||
return style; |
|||
}; |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
|
|||
onMounted(() => { |
|||
refresh(); |
|||
// 装修模式下刷新 |
|||
if (diyStore.mode == 'decorate') { |
|||
watch( |
|||
() => diyComponent.value, |
|||
(newValue, oldValue) => { |
|||
if (newValue && newValue.componentName == 'ActiveCube') { |
|||
refresh(); |
|||
} |
|||
} |
|||
) |
|||
} |
|||
}); |
|||
|
|||
const instance = getCurrentInstance(); |
|||
const height = ref(0) |
|||
|
|||
const refresh = ()=> { |
|||
nextTick(() => { |
|||
const query = uni.createSelectorQuery().in(instance); |
|||
query.select('.diy-active-cube').boundingClientRect((data: any) => { |
|||
height.value = data.height; |
|||
}).exec(); |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.active-cube-wrap { |
|||
.bd { |
|||
.item { |
|||
width: calc(46% - 20rpx); |
|||
|
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,634 @@ |
|||
<template> |
|||
<view :style="warpCss" class="goods-carousel-search-wrap"> |
|||
<view class="relative pb-[20rpx]"> |
|||
<view class="bg-img" :class="{'!-bottom-[200rpx]': diyComponent.bgGradient == true}"> |
|||
<image v-if="diyComponent.swiper.list && diyComponent.swiper.list[swiperIndex].imageUrl" :src="img(diyComponent.swiper.list[swiperIndex].imageUrl)" mode="scaleToFill" class="w-full h-full" :show-menu-by-longpress="true"/> |
|||
<view v-else class="w-full h-full bg-[#ccc]"></view> |
|||
<view class="bg-img-box" :style="bgImgBoxStyle"></view> |
|||
</view> |
|||
|
|||
<view class="fixed-wrap" :class="[ diyStore.mode != 'decorate' ? diyComponent.positionWay : '' ]" :style="fixedStyle"> |
|||
<view class="diy-search-wrap relative z-10" @click="diyStore.toRedirect(diyComponent.search.link)" :style="navbarInnerStyle"> |
|||
<view class="img-wrap" v-if="diyComponent.search.logo"> |
|||
<image :src="img(diyComponent.search.logo)" mode="aspectFit"/> |
|||
</view> |
|||
<view class="search-content"> |
|||
<input type="text" class="uni-input" placeholder-style="color:#fff" placeholder-class="!text-[#fff] text-[24rpx] leading-[68rpx]" :placeholder="isShowSearchPlaceholder ? diyComponent.search.text : ''" disabled="true"/> |
|||
<text class="nc-iconfont nc-icon-sousuo-duanV6xx1"></text> |
|||
|
|||
<swiper class="swiper-wrap" :interval="diyComponent.search.hotWord.interval * 1000" autoplay="true" vertical="true" circular="true" v-if="!isShowSearchPlaceholder"> |
|||
<swiper-item class="swiper-item" v-for="(item) in diyComponent.search.hotWord.list" :key="item.id"> |
|||
<view class=" leading-[64rpx] text-[24rpx]">{{ item.text }}</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<view class="tab-list-wrap relative z-10" v-if="diyComponent.tab.control"> |
|||
<scroll-view scroll-x="true" class="scroll-wrap" :scroll-into-view="'a' + currTabIndex"> |
|||
<view @click="changeData({ source : 'home' },-1)" class="scroll-item" :class="[{ active: currTabIndex == -1 }]"> |
|||
<view class="name" :style="{'color': getTabColor(currTabIndex == -1)}">首页</view> |
|||
<view class="line" :style="{'background-color': getTabColor(currTabIndex == -1)}" v-if="currTabIndex == -1"></view> |
|||
</view> |
|||
<view v-for="(item, index) in diyComponent.tab.list" class="scroll-item" :class="[{ active: index == currTabIndex }]" @click="changeData(item,index)" :id="'a' + index" :key="index"> |
|||
<view class="name" :style="{'color': getTabColor(index == currTabIndex)}">{{ item.text }}</view> |
|||
<view class="line" :style="{'background-color': getTabColor(index == currTabIndex)}" v-if="index == currTabIndex"></view> |
|||
</view> |
|||
</scroll-view> |
|||
<view v-if="diyComponent.tab.list.length" class="absolute tab-btn nc-iconfont nc-icon-yingyongliebiaoV6xx" @click="tabAllPopup = true"></view> |
|||
</view> |
|||
|
|||
<view class="bg-img" v-if="fixedStyleBg"> |
|||
<image v-if="diyComponent.swiper.list && diyComponent.swiper.list[swiperIndex].imageUrl" :src="img(diyComponent.swiper.list[swiperIndex].imageUrl)" mode="scaleToFill" class="w-full h-full" :show-menu-by-longpress="true"/> |
|||
<view v-else class="w-full h-full bg-[#ccc]"></view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 解决fixed定位后导航栏塌陷的问题 --> |
|||
<template v-if="diyStore.mode != 'decorate'"> |
|||
<view v-if="diyComponent.positionWay == 'fixed'" class="u-navbar-placeholder" :style="{ width: '100%', paddingTop: moduleHeight }"></view> |
|||
</template> |
|||
|
|||
<!-- 轮播图 --> |
|||
<view class="relative" :class="{'mx-[20rpx]': swiperStyle2}"> |
|||
<swiper v-if="diyComponent.swiper.control" class="swiper" :style="{ height: imgHeight }" autoplay="true" circular="true" @change="swiperChange" |
|||
:class="{ |
|||
'swiper-left': diyComponent.swiper.indicatorAlign == 'left', |
|||
'swiper-right': diyComponent.swiper.indicatorAlign == 'right', |
|||
'ns-indicator-dots': diyComponent.swiper.indicatorStyle == 'style-2' |
|||
}" |
|||
:previous-margin="swiperStyle2 ? 0 : '36rpx'" :next-margin="swiperStyle2 ? 0 : '36rpx'" |
|||
:interval="diyComponent.swiper.interval * 1000" :indicator-dots="isShowDots" |
|||
:indicator-color="diyComponent.swiper.indicatorColor" :indicator-active-color="diyComponent.swiper.indicatorActiveColor"> |
|||
<swiper-item class="swiper-item" v-for="(item,index) in diyComponent.swiper.list" :key="item.id" :style="swiperWarpCss"> |
|||
<view @click="diyStore.toRedirect(item.link)"> |
|||
<view class="item" :style="{height: imgHeight}"> |
|||
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="scaleToFill" :style="swiperWarpCss" :class="['w-full h-full',{'swiper-animation': swiperIndex != index}]" :show-menu-by-longpress="true"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" :style="swiperWarpCss" mode="scaleToFill" :class="['w-full h-full',{'swiper-animation': swiperIndex != index}]" :show-menu-by-longpress="true"/> |
|||
</view> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
<!-- #ifdef MP-WEIXIN --> |
|||
<view v-if="diyComponent.swiper.list.length > 1" :class="[ |
|||
'swiper-dot-box', |
|||
{ 'straightLine': diyComponent.swiper.indicatorStyle == 'style-2' }, |
|||
{ 'swiper-left': diyComponent.swiper.indicatorAlign == 'left' }, |
|||
{ 'swiper-right': diyComponent.swiper.indicatorAlign == 'right' } |
|||
]"> |
|||
<view v-for="(numItem, numIndex) in diyComponent.swiper.list" :key="numIndex" :class="['swiper-dot', { active: numIndex == swiperIndex }]" :style="[numIndex == swiperIndex ? { backgroundColor: diyComponent.swiper.indicatorActiveColor } : { backgroundColor: diyComponent.swiper.indicatorColor }]"></view> |
|||
</view> |
|||
<!-- #endif --> |
|||
</view> |
|||
|
|||
<!-- 分类展开 --> |
|||
<u-popup :safeAreaInsetTop="true" :show="tabAllPopup" mode="top" @close="tabAllPopup = false"> |
|||
<view class="text-sm px-[30rpx] pt-3" :style="{'padding-top':(menuButtonInfo.top+'px')}">全部分类</view> |
|||
<view class="flex flex-wrap pl-[30rpx] pt-[30rpx]"> |
|||
<view @click="changeData({ source : 'home' },-1)" :class="['px-[26rpx] border-[2rpx] border-solid border-transparent h-[60rpx] mr-[30rpx] mb-[30rpx] flex items-center justify-center bg-[#F4F4F4] rounded-[8rpx] text-xs', { 'tab-select-popup': currTabIndex == -1 }]"> |
|||
首页 |
|||
</view> |
|||
<text @click="changeData(item,index)" v-for="(item, index) in diyComponent.tab.list" :key="index" |
|||
:class="['px-[26rpx] border-[2rpx] border-solid border-transparent h-[60rpx] mr-[30rpx] mb-[30rpx] flex items-center justify-center bg-[#F4F4F4] rounded-[8rpx] text-xs', { 'tab-select-popup': index == currTabIndex }]"> |
|||
{{ item.text }} |
|||
</text> |
|||
</view> |
|||
</u-popup> |
|||
</view> |
|||
|
|||
<!-- 展示微页面数据 --> |
|||
<template v-if="currentSource == 'diy_page'"> |
|||
|
|||
<view class="child-diy-template-wrap bg-index"> |
|||
|
|||
<diy-group :data="diyPageData"></diy-group> |
|||
|
|||
</view> |
|||
</template> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// 轮播搜索 |
|||
import { ref, reactive, computed, watch, onMounted, nextTick, getCurrentInstance } from 'vue'; |
|||
import { img } from '@/utils/common'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
import diyGroup from '@/addon/components/diy/group/index.vue' |
|||
import { getDiyInfo } from '@/app/api/diy'; |
|||
|
|||
const instance = getCurrentInstance(); |
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount', 'global']); |
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
|
|||
const moduleHeight:any = ref('') |
|||
|
|||
const setModuleLocation = ()=> { |
|||
nextTick(() => { |
|||
setTimeout(()=>{ |
|||
const query = uni.createSelectorQuery().in(instance); |
|||
query.select('.fixed-wrap').boundingClientRect((data:any) => { |
|||
moduleHeight.value = (data.height || 0) + 'px'; |
|||
}).exec(); |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
const fixedStyleBg = ref(false); |
|||
const fixedStyle = computed(()=>{ |
|||
if (diyStore.mode == 'decorate') return ''; |
|||
var style = ''; |
|||
// #ifdef H5 |
|||
if(props.global.topStatusBar.isShow && props.global.topStatusBar.style == 'style-4') { |
|||
style += 'top:' + diyStore.topTabarHeight + 'px;'; |
|||
} |
|||
// #endif |
|||
if(diyComponent.value.positionWay == 'fixed') { |
|||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ |
|||
menuButtonInfo = uni.getMenuButtonBoundingClientRect(); |
|||
if(props.global.topStatusBar.isShow) { |
|||
style += 'top:' + diyStore.topTabarHeight + 'px;'; |
|||
} |
|||
// #endif |
|||
|
|||
fixedStyleBg.value = false; |
|||
if (diyStore.scrollTop > 20) { |
|||
let str = diyComponent.value.fixedBgColor; |
|||
let arr = str.split(','); |
|||
let num = diyComponent.value.fixedBgColor ? parseInt(arr[arr.length-1]) : 0; |
|||
if(!diyComponent.value.fixedBgColor || num == 0 ){ |
|||
fixedStyleBg.value = true; |
|||
}else{ |
|||
fixedStyleBg.value = false; |
|||
style += 'background-color:' + diyComponent.value.fixedBgColor + ';'; |
|||
} |
|||
} |
|||
} |
|||
return style; |
|||
}) |
|||
|
|||
const getTabColor = (flag:any)=>{ |
|||
let color = ''; |
|||
if(flag){ |
|||
color = diyComponent.value.tab.selectColor; |
|||
if(diyComponent.value.positionWay == 'fixed' && diyStore.scrollTop > 20) { |
|||
color = diyComponent.value.tab.fixedSelectColor; |
|||
} |
|||
}else{ |
|||
color = diyComponent.value.tab.noColor; |
|||
if(diyComponent.value.positionWay == 'fixed' && diyStore.scrollTop > 20) { |
|||
color = diyComponent.value.tab.fixedNoColor; |
|||
} |
|||
} |
|||
|
|||
return color; |
|||
} |
|||
|
|||
const isShowSearchPlaceholder = computed(()=> { |
|||
let flag = true; |
|||
for (let i = 0; i < diyComponent.value.search.hotWord.list.length; i++) { |
|||
let item = diyComponent.value.search.hotWord.list[i]; |
|||
if (item.text) { |
|||
flag = false; |
|||
break; |
|||
} |
|||
} |
|||
return flag; |
|||
}) |
|||
|
|||
// 背景渐变 |
|||
const bgImgBoxStyle = computed(()=>{ |
|||
var style = ''; |
|||
let str = props.global.pageStartBgColor ? props.global.pageStartBgColor : 'rgba(255,255,255,1)'; |
|||
if(str.indexOf('(') > -1) { |
|||
let arr = str.split('(')[1].split(')')[0].split(','); |
|||
if (diyComponent.value.bgGradient == true) { |
|||
style += `background: linear-gradient(rgba(${arr[0]}, ${arr[1]}, ${arr[2]}, 0) 65%, rgba(${arr[0]}, ${arr[1]}, ${arr[2]}, 0.6) 70%, rgba(${arr[0]}, ${arr[1]}, ${arr[2]}, 0.85) 80%, rgba(${arr[0]}, ${arr[1]}, ${arr[2]}, 0.95) 90%, rgb(${arr[0]}, ${arr[1]}, ${arr[2]}, 1) 100%);`; |
|||
} |
|||
}else{ |
|||
style += `background: (${str});`; |
|||
} |
|||
return style; |
|||
}); |
|||
|
|||
// 轮播样式二 |
|||
const swiperStyle2 = computed(()=>{ |
|||
var style = false; |
|||
style = diyComponent.value.swiper.swiperStyle == 'style-2' ? true : false; |
|||
return style; |
|||
}) |
|||
|
|||
const imgHeight = computed(() => { |
|||
return (diyComponent.value.swiper.imageHeight * 2) + 'rpx'; |
|||
}) |
|||
|
|||
const swiperIndex = ref(0); |
|||
|
|||
const swiperChange = e => { |
|||
swiperIndex.value = e.detail.current; |
|||
}; |
|||
|
|||
const swiperWarpCss = computed(() => { |
|||
var style = ''; |
|||
if (diyComponent.value.swiper.topRounded) style += 'border-top-left-radius:' + diyComponent.value.swiper.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.swiper.topRounded) style += 'border-top-right-radius:' + diyComponent.value.swiper.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.swiper.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.swiper.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.swiper.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.swiper.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
const currTabIndex = ref(-1) |
|||
|
|||
const currentSource = ref('') |
|||
|
|||
const changeData = (item:any,index:any)=> { |
|||
if (diyStore.mode == 'decorate') return false; |
|||
currentSource.value = item.source; |
|||
currTabIndex.value = index; |
|||
if(item.source == 'home'){ |
|||
|
|||
// 首页 |
|||
diyStore.topFixedStatus = 'home' |
|||
|
|||
}else if (item.source == 'diy_page') { |
|||
|
|||
// 查询微页面数据 |
|||
diyStore.topFixedStatus = 'diy' |
|||
getDiyInfoFn(item.diy_id); |
|||
|
|||
} |
|||
} |
|||
|
|||
let tabAllPopup = ref(false); |
|||
let menuButtonInfo:any = {}; |
|||
let navbarInnerStyle = ref('') |
|||
|
|||
onMounted(() => { |
|||
refresh(); |
|||
// 装修模式下刷新 |
|||
if (diyStore.mode == 'decorate') { |
|||
watch( |
|||
() => diyComponent.value, |
|||
(newValue, oldValue) => { |
|||
if (newValue && newValue.componentName == 'CarouselSearch') { |
|||
refresh(); |
|||
} |
|||
} |
|||
) |
|||
} |
|||
|
|||
// 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API,尚未兼容) |
|||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ |
|||
if(diyComponent.value.positionWay == 'fixed') { |
|||
menuButtonInfo = uni.getMenuButtonBoundingClientRect(); |
|||
// 导航栏内部盒子的样式 |
|||
// 导航栏宽度,如果在小程序下,导航栏宽度为胶囊的左边到屏幕左边的距离 |
|||
// 如果是各家小程序,导航栏内部的宽度需要减少右边胶囊的宽度 |
|||
if(props.global.topStatusBar.isShow == false) { |
|||
let rightButtonWidth = menuButtonInfo.width ? menuButtonInfo.width * 2 + 'rpx' : '70rpx'; |
|||
navbarInnerStyle.value += 'padding-right:calc(' + rightButtonWidth + ' + 30rpx);'; |
|||
navbarInnerStyle.value += 'padding-top:' + menuButtonInfo.top + 'px;'; |
|||
} |
|||
|
|||
} |
|||
// #endif |
|||
|
|||
}); |
|||
|
|||
const refresh = ()=> { |
|||
setModuleLocation(); |
|||
changeData({ source : 'home' },-1) |
|||
diyComponent.value.swiper.list.forEach((item : any) => { |
|||
if (item.imageUrl == '') { |
|||
item.imgWidth = 690; |
|||
item.imgHeight = 330; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
const diyPageData = reactive({ |
|||
pageMode: 'diy', |
|||
title: '', |
|||
global: <any>{}, |
|||
value: [] |
|||
}) |
|||
|
|||
const getDiyInfoFn = (id:any) => { |
|||
if(!id){ |
|||
diyPageData.pageMode = 'diy'; |
|||
diyPageData.title = ''; |
|||
diyPageData.global = {}; |
|||
diyPageData.value = []; |
|||
return; |
|||
} |
|||
getDiyInfo({ |
|||
id |
|||
}).then((res : any) => { |
|||
if (res.data.value) { |
|||
let data = res.data; |
|||
diyPageData.pageMode = data.mode; |
|||
diyPageData.title = data.title; |
|||
|
|||
let sources = JSON.parse(data.value); |
|||
diyPageData.global = sources.global; |
|||
diyPageData.global.topStatusBar.isShow = false; // 子页面不需要展示顶部导航栏 |
|||
diyPageData.global.bottomTabBarSwitch = false; // 子页面不需要展示底部导航 |
|||
diyPageData.value = sources.value; |
|||
|
|||
diyPageData.value.forEach((item, index) => { |
|||
item.pageStyle = ''; |
|||
if(item.pageStartBgColor) { |
|||
if (item.pageStartBgColor && item.pageEndBgColor) item.pageStyle += `background:linear-gradient(${item.pageGradientAngle},${item.pageStartBgColor},${item.pageEndBgColor});`; |
|||
else item.pageStyle += 'background-color:' + item.pageStartBgColor + ';'; |
|||
} |
|||
|
|||
if (item.margin) { |
|||
if (item.margin.top > 0) { |
|||
item.pageStyle += 'padding-top:' + item.margin.top * 2 + 'rpx' + ';'; |
|||
} |
|||
item.pageStyle += 'padding-bottom:' + item.margin.bottom * 2 + 'rpx' + ';'; |
|||
item.pageStyle += 'padding-right:' + item.margin.both * 2 + 'rpx' + ';'; |
|||
item.pageStyle += 'padding-left:' + item.margin.both * 2 + 'rpx' + ';'; |
|||
} |
|||
}); |
|||
uni.setNavigationBarTitle({ |
|||
title: diyPageData.title |
|||
}) |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// 轮播指示器 |
|||
let isShowDots = ref(true) |
|||
// #ifdef H5 |
|||
isShowDots.value = true; |
|||
// #endif |
|||
|
|||
// #ifdef MP-WEIXIN |
|||
isShowDots.value = false; |
|||
// #endif |
|||
|
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.goods-carousel-search-wrap{ |
|||
.bg-img{ |
|||
position: absolute; |
|||
width: 100%; |
|||
top: 0; |
|||
bottom: 0; |
|||
z-index: 0; |
|||
-webkit-filter: blur(0); |
|||
filter: blur(0); |
|||
overflow: hidden; |
|||
uni-image, image{ |
|||
-webkit-filter: blur(15px); |
|||
filter: blur(15px); |
|||
-webkit-transform: scale(1.5); |
|||
transform: scale(1.5); |
|||
} |
|||
.bg-img-box{ |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.fixed-wrap { |
|||
&.fixed { |
|||
position: fixed; |
|||
left: 0; |
|||
right: 0; |
|||
top: 0; |
|||
z-index: 991; |
|||
transition: background .3s; |
|||
} |
|||
} |
|||
|
|||
.diy-search-wrap{ |
|||
display: flex; |
|||
position: relative; |
|||
align-items: center; |
|||
padding:20rpx; |
|||
.img-wrap{ |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
width: 140rpx; |
|||
height: 60rpx; |
|||
margin-right: 20rpx; |
|||
image{ |
|||
width: 100%; |
|||
height:100%; |
|||
} |
|||
} |
|||
|
|||
.search-content { |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 0 40rpx; |
|||
border-radius: 50rpx; |
|||
background-color: rgba(255,255,255,.2); |
|||
flex: 1; |
|||
position: relative; |
|||
input, .uni-input { |
|||
box-sizing: border-box; |
|||
display: block; |
|||
height: 64rpx; |
|||
line-height: 68rpx; |
|||
width: 100%; |
|||
padding-right: 20rpx; |
|||
color: #fff; |
|||
background: none; |
|||
} |
|||
.iconfont { |
|||
font-size: 30rpx; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
} |
|||
.swiper-wrap{ |
|||
position: absolute; |
|||
width:80%; |
|||
height: 64rpx; |
|||
line-height: 64rpx; |
|||
color:#fff; |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
.tab-list-wrap { |
|||
.scroll-wrap { |
|||
left: 0; |
|||
right: 0; |
|||
z-index: 5; |
|||
width: 100%; |
|||
white-space: nowrap; |
|||
box-sizing: border-box; |
|||
padding: 20rpx 80rpx 20rpx 20rpx; |
|||
} |
|||
.scroll-item { |
|||
display: inline-block; |
|||
text-align: center; |
|||
vertical-align: top; |
|||
width: auto; |
|||
position: relative; |
|||
padding: 0 20rpx; |
|||
|
|||
.name { |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
line-height: 38rpx; |
|||
margin-bottom: 10rpx; |
|||
} |
|||
|
|||
&.active { |
|||
position: relative; |
|||
.name { |
|||
font-size: 32rpx; |
|||
line-height: 38rpx; |
|||
font-weight: bold; |
|||
} |
|||
.line{ |
|||
position: absolute; |
|||
bottom: 0; |
|||
width: 34rpx; |
|||
height: 4rpx; |
|||
border-radius: 29%; |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
} |
|||
} |
|||
} |
|||
.tab-btn{ |
|||
font-size: 34rpx; |
|||
/* #ifdef H5 */ |
|||
top: 22rpx; |
|||
right: 20rpx; |
|||
line-height: 1; |
|||
color: #fff; |
|||
&::after{ |
|||
content: ""; |
|||
position: absolute; |
|||
top: 6rpx; |
|||
bottom: -2rpx; |
|||
left: -14rpx; |
|||
width: 4rpx; |
|||
background: linear-gradient( 180deg, #FFFFFF 16%, rgba(255,255,255,0) 92%); |
|||
} |
|||
/* #endif */ |
|||
/* #ifdef MP-WEIXIN */ |
|||
top: 24rpx; |
|||
right: 20rpx; |
|||
color: #fff; |
|||
&::after{ |
|||
content: ""; |
|||
position: absolute; |
|||
top: 2rpx; |
|||
bottom: 0; |
|||
left: -16rpx; |
|||
width: 4rpx; |
|||
background: linear-gradient( 180deg, #FFFFFF 16%, rgba(255,255,255,0) 92%); |
|||
} |
|||
/* #endif */ |
|||
} |
|||
} |
|||
.tab-select-popup{ |
|||
color: var(--primary-color); |
|||
border-color: var(--primary-color); |
|||
background-color: var(--primary-color-light); |
|||
} |
|||
.swiper-animation{ |
|||
transform: scale(0.94, 0.94); |
|||
transition-duration: 0.3s; |
|||
transition-timing-function: ease; |
|||
} |
|||
// 轮播指示器 |
|||
.swiper-right :deep(.uni-swiper-dots-horizontal) { |
|||
right: 80rpx; |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
transform: translate(0); |
|||
} |
|||
.swiper-left :deep(.uni-swiper-dots-horizontal) { |
|||
left: 80rpx; |
|||
transform: translate(0); |
|||
} |
|||
.swiper.ns-indicator-dots :deep(.uni-swiper-dot) { |
|||
width: 18rpx; |
|||
height: 6rpx; |
|||
border-radius: 4rpx; |
|||
} |
|||
.swiper.ns-indicator-dots :deep(.uni-swiper-dot-active) { |
|||
width: 36rpx; |
|||
} |
|||
.swiper-dot-box { |
|||
position: absolute; |
|||
bottom: 20rpx; |
|||
width: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 0 80rpx 8rpx; |
|||
box-sizing: border-box; |
|||
|
|||
&.swiper-left { |
|||
justify-content: flex-start; |
|||
} |
|||
|
|||
&.swiper-right { |
|||
justify-content: flex-end; |
|||
} |
|||
|
|||
.swiper-dot { |
|||
background-color: #b2b2b2; |
|||
width: 15rpx; |
|||
border-radius: 50%; |
|||
height: 15rpx; |
|||
margin: 8rpx; |
|||
} |
|||
|
|||
&.straightLine { |
|||
.swiper-dot { |
|||
width: 18rpx; |
|||
height: 6rpx; |
|||
border-radius: 4rpx; |
|||
|
|||
&.active { |
|||
width: 36rpx; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,113 @@ |
|||
<template> |
|||
<view :style="warpCss"> |
|||
<view :class="['float-btn flex flex-col z-1000 items-center px-[24rpx] fixed', diyComponent.bottomPosition, diyStore.mode == 'decorate' ? 'float-btn-border' : '']" :style="floatBtnWrapCss"> |
|||
<view v-for="(item,index) in diyComponent.list" :key="index" @click="diyStore.toRedirect(item.link)" :class="{'flex items-center justify-center' : true, 'mb-[20rpx]': diyComponent.list.length != index+1 }" :style="floatBtnItemCss"> |
|||
<image v-if="item && item.imageUrl" :style="floatBtnItemCss" :src="img(item.imageUrl)" mode="aspectFit"></image> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="aspectFit" :style="floatBtnItemCss"/> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// 浮动按钮组件 |
|||
import { computed, watch } from 'vue'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
import { img } from '@/utils/common'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
|
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
const floatBtnItemCss = computed(() => { |
|||
var style = ''; |
|||
style += 'width:' + diyComponent.value.imageSize * 2 + 'rpx;'; |
|||
style += 'height:' + diyComponent.value.imageSize * 2 + 'rpx;'; |
|||
style += 'border-radius:' + diyComponent.value.aroundRadius * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
const floatBtnWrapCss = computed(() => { |
|||
let style = ''; |
|||
if(diyComponent.value.offset){ |
|||
if(diyComponent.value.bottomPosition == 'lowerRight' || diyComponent.value.bottomPosition == 'lowerLeft'){ |
|||
style += 'padding-bottom:'+ diyComponent.value.offset * 2 + 'rpx;'; |
|||
}else if(diyComponent.value.bottomPosition == 'upperRight' || diyComponent.value.bottomPosition == 'upperLeft'){ |
|||
style += 'padding-top:'+ diyComponent.value.offset * 2 + 'rpx;'; |
|||
} |
|||
} |
|||
return style; |
|||
}) |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.float-btn{ |
|||
&.decorate-position.upperRight,&.decorate-position.lowerRight { |
|||
align-items: flex-end; |
|||
} |
|||
&.decorate-position.upperLeft,&.decorate-position.lowerLeft { |
|||
align-items: baseline; |
|||
} |
|||
&.upperLeft { |
|||
top: 100rpx; |
|||
left: 30rpx; |
|||
} |
|||
|
|||
&.upperRight { |
|||
top: 100rpx; |
|||
right: 30rpx; |
|||
} |
|||
|
|||
&.lowerLeft { |
|||
bottom: 160rpx; |
|||
left: 30rpx; |
|||
padding-bottom: constant(safe-area-inset-bottom); |
|||
/*兼容 IOS<11.2*/ |
|||
padding-bottom: env(safe-area-inset-bottom); |
|||
/*兼容 IOS>11.2*/ |
|||
} |
|||
|
|||
&.lowerRight { |
|||
bottom: 160rpx; |
|||
right: 30rpx; |
|||
padding-bottom: constant(safe-area-inset-bottom); |
|||
/*兼容 IOS<11.2*/ |
|||
padding-bottom: env(safe-area-inset-bottom); |
|||
/*兼容 IOS>11.2*/ |
|||
} |
|||
} |
|||
.z-1000{ |
|||
z-index: 1000; |
|||
} |
|||
.float-btn-border{ |
|||
border: 4rpx dashed var(--primary-color); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,257 @@ |
|||
<template> |
|||
<view :style="warpCss"> |
|||
<view :style="maskLayer"></view> |
|||
<view class="diy-graphic-nav relative"> |
|||
<view v-if="diyComponent.layout == 'vertical'" class="graphic-nav"> |
|||
<view class="graphic-nav-item" v-for="(item, index) in diyComponent.list" :key="item.id"> |
|||
|
|||
<view @click="diyStore.toRedirect(item.link)" :class="['flex items-center justify-between py-3 px-4', index == 0 ? 'border-t-0':'border-t']"> |
|||
|
|||
<view class="graphic-img relative flex items-center w-10 h-10 mr-[20rpx]" |
|||
v-if="diyComponent.mode != 'text'" |
|||
:style="{ width: diyComponent.imageSize * 2 + 'rpx', height: diyComponent.imageSize * 2 + 'rpx' }"> |
|||
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="aspectFill" |
|||
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="aspectFill" |
|||
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/> |
|||
|
|||
<text v-if="item.label.control" |
|||
class="tag absolute -top-[10rpx] -right-[24rpx] text-white rounded-[24rpx] rounded-bl-none transform scale-80 py-1 px-2 text-xs" |
|||
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }"> |
|||
{{ item.label.text }} |
|||
</text> |
|||
</view> |
|||
|
|||
<text v-if="diyComponent.mode != 'img'" class="graphic-text w-full truncate leading-normal" |
|||
:style="{ fontSize: diyComponent.font.size * 2 + 'rpx', fontWeight: diyComponent.font.weight, color: diyComponent.font.color }"> |
|||
{{ item.title }} |
|||
</text> |
|||
<u-icon name="arrow-right" color="#999999" size="12"></u-icon> |
|||
|
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
</view> |
|||
<swiper v-else-if="diyComponent.layout == 'horizontal' && diyComponent.showStyle == 'pageSlide'" |
|||
class="graphic-nav box-border relative" circular :indicator-dots="false" |
|||
:style="{ height: swiperHeight }" @change="swiperChange"> |
|||
<swiper-item class="graphic-nav-wrap flex flex-wrap" v-for="(numItem, numIndex) in Math.ceil(diyComponent.list.length / (diyComponent.pageCount * diyComponent.rowCount))"> |
|||
|
|||
<template v-for="(item, index) in diyComponent.list"> |
|||
|
|||
<view class="graphic-nav-item" :class="[diyComponent.mode]" :key="item.id" v-if="swiperCondition(index,numItem)" :style="{ width: 100 / diyComponent.rowCount + '%' }"> |
|||
|
|||
<view @click="diyStore.toRedirect(item.link)" class="flex flex-col items-center box-border py-2"> |
|||
|
|||
<view class="graphic-img relative flex items-center justify-center w-10 h-10" |
|||
v-if="diyComponent.mode != 'text'" |
|||
:style="{ width: diyComponent.imageSize * 2 + 'rpx', height: diyComponent.imageSize * 2 + 'rpx' }"> |
|||
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="aspectFill" |
|||
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="aspectFill" |
|||
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/> |
|||
|
|||
<text |
|||
class="tag absolute -top-[10rpx] -right-[24rpx] text-white rounded-[24rpx] rounded-bl-none transform scale-80 py-1 px-2 text-xs" |
|||
v-if="item.label.control" |
|||
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }"> |
|||
{{ item.label.text }} |
|||
</text> |
|||
</view> |
|||
|
|||
<text v-if="diyComponent.mode != 'img'" |
|||
class="graphic-text w-full text-center truncate leading-normal" |
|||
:class="{ 'pt-1.5' : diyComponent.mode != 'text' }" |
|||
:style="{ fontSize: diyComponent.font.size * 2 + 'rpx', fontWeight: diyComponent.font.weight, color: diyComponent.font.color }"> |
|||
{{ item.title }} |
|||
</text> |
|||
</view> |
|||
|
|||
</view> |
|||
</template> |
|||
</swiper-item> |
|||
</swiper> |
|||
|
|||
<scroll-view v-else :scroll-x="diyComponent.showStyle == 'singleSlide'" :class="['graphic-nav','graphic-nav-' + diyComponent.showStyle]" class=" py-[10rpx]"> |
|||
<!-- #ifdef MP --> |
|||
<view class="uni-scroll-view-content"> |
|||
<!-- #endif --> |
|||
|
|||
<view class="graphic-nav-item" :class="{'flex-shrink-0' : diyComponent.showStyle == 'singleSlide'}" |
|||
v-for="(item, index) in diyComponent.list" :key="item.id" |
|||
:style="{ width: 100 / diyComponent.rowCount + '%' }"> |
|||
|
|||
<view @click="diyStore.toRedirect(item.link)" class="flex flex-col items-center box-border py-2"> |
|||
<view class="graphic-img relative flex items-center justify-center w-10 h-10" |
|||
v-if="diyComponent.mode != 'text'" |
|||
:style="{ width: diyComponent.imageSize * 2 + 'rpx', height: diyComponent.imageSize * 2 + 'rpx' }"> |
|||
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="aspectFill" |
|||
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="aspectFill" |
|||
:style="{ maxWidth: diyComponent.imageSize * 2 + 'rpx', maxHeight: diyComponent.imageSize * 2 + 'rpx', borderRadius: diyComponent.aroundRadius * 2 + 'rpx' }"/> |
|||
<text |
|||
:class="['tag absolute -top-[10rpx] -right-[24rpx] text-white rounded-[24rpx] rounded-bl-none transform scale-80 py-1 px-2 text-xs']" |
|||
v-if="item.label.control" |
|||
:style="{ color: item.label.textColor, backgroundImage: 'linear-gradient(' + item.label.bgColorStart + ',' + item.label.bgColorEnd + ')' }"> |
|||
{{ item.label.text }} |
|||
</text> |
|||
</view> |
|||
<text v-if="diyComponent.mode != 'img'" |
|||
class="graphic-text w-full text-center truncate leading-normal" |
|||
:class="{ 'pt-1.5' : diyComponent.mode != 'text' }" |
|||
:style="{ fontSize: diyComponent.font.size * 2 + 'rpx', fontWeight: diyComponent.font.weight, color: diyComponent.font.color }"> |
|||
{{ item.title }} |
|||
</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- #ifdef MP --> |
|||
</view> |
|||
<!-- #endif --> |
|||
|
|||
</scroll-view> |
|||
|
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
// 图文导航 |
|||
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue'; |
|||
import { img } from '@/utils/common'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
|
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
style += 'position:relative;'; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
|
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`; |
|||
style += 'background-size: cover;background-repeat: no-repeat;'; |
|||
} |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
// 背景图加遮罩层 |
|||
const maskLayer = computed(()=>{ |
|||
var style = ''; |
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += 'position:absolute;top:0;width:100%;'; |
|||
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`; |
|||
style += `height:${height.value}px;`; |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
} |
|||
|
|||
return style; |
|||
}); |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
|
|||
const swiperIndex = ref(0); |
|||
|
|||
const swiperChange = e => { |
|||
swiperIndex.value = e.detail.current; |
|||
}; |
|||
|
|||
const swiperCondition = (index, numItem) => { |
|||
let count = diyComponent.value.pageCount * diyComponent.value.rowCount; |
|||
let result = true; |
|||
|
|||
// #ifdef MP-WEIXIN |
|||
result = index >= [(numItem) * (count)] && index < [(numItem + 1) * (count)]; |
|||
// #endif |
|||
|
|||
// #ifdef H5 |
|||
result = index >= [(numItem - 1) * (count)] && index < [numItem * (count)]; |
|||
// #endif |
|||
|
|||
return result; |
|||
} |
|||
|
|||
const swiperHeight = ref(''); |
|||
|
|||
const handleData = () => { |
|||
if(diyComponent.value.layout == 'horizontal' && diyComponent.value.showStyle == 'pageSlide') { |
|||
var height = 0; |
|||
const query = uni.createSelectorQuery().in(instance); |
|||
query.select('.graphic-nav-item').boundingClientRect((data: any) => { |
|||
height = data.height * diyComponent.value.pageCount; |
|||
swiperHeight.value = (height * 2) + 'rpx'; |
|||
}).exec(); |
|||
} |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
refresh(); |
|||
// 装修模式下刷新 |
|||
if (diyStore.mode == 'decorate') { |
|||
watch( |
|||
() => diyComponent.value, |
|||
(newValue, oldValue) => { |
|||
if (newValue && newValue.componentName == 'GraphicNav') { |
|||
refresh(); |
|||
} |
|||
} |
|||
) |
|||
} |
|||
}); |
|||
|
|||
const instance = getCurrentInstance(); |
|||
const height = ref(0) |
|||
|
|||
const refresh = () => { |
|||
nextTick(() => { |
|||
handleData() |
|||
const query = uni.createSelectorQuery().in(instance); |
|||
query.select('.diy-graphic-nav').boundingClientRect((data: any) => { |
|||
height.value = data.height; |
|||
}).exec(); |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
/* 固定显示 */ |
|||
.graphic-nav-fixed>>>.uni-scroll-view-content { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
} |
|||
|
|||
/* 单行滑动 */ |
|||
.graphic-nav-singleSlide>>>.uni-scroll-view-content { |
|||
display: flex; |
|||
} |
|||
</style> |
|||
<style lang="scss" scoped> |
|||
</style> |
|||
@ -0,0 +1,44 @@ |
|||
<template> |
|||
<view :style="warpCss"></view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// 辅助空白 |
|||
import { computed, watch } from 'vue'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
|
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
style += 'height:' + diyComponent.value.height * 2 + 'rpx;'; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
</script> |
|||
|
|||
<style></style> |
|||
@ -0,0 +1,40 @@ |
|||
<template> |
|||
<view class="horz-line-wrap"> |
|||
<view v-if="diyStore.mode == 'decorate'" class="h-[30rpx]"></view> |
|||
<view :style="warpCss"></view> |
|||
<view v-if="diyStore.mode == 'decorate'" class="h-[30rpx]"></view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// 辅助线 |
|||
import { computed, watch } from 'vue'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
|
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
style += 'border-top:' + (diyComponent.value.borderWidth * 2) + 'rpx ' + diyComponent.value.borderStyle + ' ' + diyComponent.value.borderColor + ';'; |
|||
return style; |
|||
}) |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
</script> |
|||
|
|||
<style></style> |
|||
@ -0,0 +1,91 @@ |
|||
<template> |
|||
<view :style="warpCss"> |
|||
<view class="simple-graph-wrap overflow-hidden relative leading-0"> |
|||
<image v-if="diyComponent.imageUrl" :style="itemCss" :src="img(diyComponent.imageUrl)" mode="widthFix" :show-menu-by-longpress="true" class="w-full"/> |
|||
<image v-else :style="itemCss" :src="img('static/resource/images/diy/figure.png')" mode="widthFix" :show-menu-by-longpress="true" class="w-full"/> |
|||
|
|||
<template v-if="diyStore.mode != 'decorate'"> |
|||
<!-- 热区功能 --> |
|||
<view @click="diyStore.toRedirect(mapItem.link)" class="absolute" v-for="(mapItem, mapIndex) in diyComponent.heatMapData" |
|||
:key="mapIndex" :style="{ |
|||
width: mapItem.width + '%', |
|||
height: mapItem.height + '%', |
|||
left: mapItem.left + '%', |
|||
top: mapItem.top + '%' |
|||
}"></view> |
|||
|
|||
</template> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// 热区 |
|||
import { computed, watch, onMounted } from 'vue'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
import { img } from '@/utils/common'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
|
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
return style; |
|||
}) |
|||
|
|||
const itemCss = computed(() => { |
|||
var style = ''; |
|||
style += 'height:' + diyComponent.value.imgHeight + ';'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
onMounted(() => { |
|||
refresh(); |
|||
// 装修模式下刷新 |
|||
watch( |
|||
() => diyComponent.value, |
|||
(newValue, oldValue) => { |
|||
if (newValue && newValue.componentName == 'HotArea') { |
|||
refresh(); |
|||
} |
|||
} |
|||
) |
|||
}); |
|||
|
|||
const refresh = () => { |
|||
if (diyStore.mode == 'decorate') { |
|||
// 装修模式下设置默认图 |
|||
if (diyComponent.value.imageUrl == '') { |
|||
diyComponent.value.imgWidth = 690; |
|||
diyComponent.value.imgHeight = 330; |
|||
} |
|||
} |
|||
} |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
</style> |
|||
@ -0,0 +1,140 @@ |
|||
<template> |
|||
<view :style="warpCss"> |
|||
<view :style="maskLayer"></view> |
|||
<view class="diy-image-ads"> |
|||
<view v-if="diyComponent.list.length == 1" class="leading-0 overflow-hidden" :style="swiperWarpCss"> |
|||
<view @click="diyStore.toRedirect(diyComponent.list[0].link)"> |
|||
<image v-if="diyComponent.list[0].imageUrl" :src="img(diyComponent.list[0].imageUrl)" :style="{height: imgHeight}" mode="heightFix" class="!w-full" :show-menu-by-longpress="true"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" :style="{height: imgHeight}" mode="heightFix" class="!w-full" :show-menu-by-longpress="true"/> |
|||
</view> |
|||
</view> |
|||
|
|||
<swiper v-else class="swiper" :style="{ height: imgHeight }" autoplay="true" circular="true" @change="swiperChange"> |
|||
<swiper-item class="swiper-item" v-for="(item) in diyComponent.list" :key="item.id" :style="swiperWarpCss"> |
|||
<view @click="diyStore.toRedirect(item.link)"> |
|||
<view class="item" :style="{height: imgHeight}"> |
|||
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="scaleToFill" class="w-full h-full" :show-menu-by-longpress="true"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" class="w-full h-full" :show-menu-by-longpress="true"/> |
|||
</view> |
|||
</view> |
|||
</swiper-item> |
|||
</swiper> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
// 图片广告 |
|||
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
import { img } from '@/utils/common'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
|
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
style += 'position:relative;'; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
|
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`; |
|||
style += 'background-size: cover;background-repeat: no-repeat;'; |
|||
} |
|||
return style; |
|||
}) |
|||
|
|||
const swiperWarpCss = computed(() => { |
|||
var style = ''; |
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
// 背景图加遮罩层 |
|||
const maskLayer = computed(()=>{ |
|||
var style = ''; |
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += 'position:absolute;top:0;width:100%;'; |
|||
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`; |
|||
style += `height:${height.value}px;`; |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
} |
|||
|
|||
return style; |
|||
}); |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
|
|||
const imgHeight = computed(() => { |
|||
return (diyComponent.value.imageHeight * 2) + 'rpx'; |
|||
}) |
|||
|
|||
const swiperIndex = ref(0); |
|||
|
|||
const swiperChange = e => { |
|||
swiperIndex.value = e.detail.current; |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
refresh(); |
|||
// 装修模式下刷新 |
|||
if (diyStore.mode == 'decorate') { |
|||
watch( |
|||
() => diyComponent.value, |
|||
(newValue, oldValue) => { |
|||
if (newValue && newValue.componentName == 'ImageAds') { |
|||
refresh(); |
|||
} |
|||
} |
|||
) |
|||
} |
|||
}); |
|||
|
|||
const instance = getCurrentInstance(); |
|||
const height = ref(0) |
|||
|
|||
const refresh = () => { |
|||
// 装修模式下设置默认图 |
|||
if (diyStore.mode == 'decorate') { |
|||
diyComponent.value.list.forEach((item : any) => { |
|||
if (item.imageUrl == '') { |
|||
item.imgWidth = 690; |
|||
item.imgHeight = 330; |
|||
} |
|||
}); |
|||
} |
|||
nextTick(() => { |
|||
const query = uni.createSelectorQuery().in(instance); |
|||
query.select('.diy-image-ads').boundingClientRect((data: any) => { |
|||
height.value = data.height; |
|||
}).exec(); |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
</style> |
|||
@ -0,0 +1,185 @@ |
|||
<template> |
|||
<view :style="warpCss"> |
|||
<view class="pt-[34rpx] member-info"> |
|||
<!-- #ifdef MP-WEIXIN --> |
|||
<!-- 解决fixed定位后导航栏塌陷的问题 --> |
|||
<view :style="navbarInnerStyle"></view> |
|||
<!-- #endif --> |
|||
<view v-if="info" class="flex ml-[32rpx] mr-[52rpx] items-center relative"> |
|||
<!-- 唤起获取微信 --> |
|||
<u-avatar :src="img(info.headimg)" size="55" leftIcon="none" @click="clickAvatar"></u-avatar> |
|||
<view class="ml-[22rpx]"> |
|||
<view class="text-[#222222] flex pr-[50rpx] flex-wrap items-center"> |
|||
<view class="text-[#222222] truncate max-w-[320rpx] font-bold text-lg mr-[16rpx]" :style="{ color : diyComponent.textColor }">{{ info.nickname }}</view> |
|||
</view> |
|||
<view class="text-[#696B70] text-[24rpx] mt-[10rpx]" :style="{ color : diyComponent.textColor }">UID:{{ info.member_no }}</view> |
|||
</view> |
|||
<view class="set-icon flex items-center absolute right-0 top-2"> |
|||
<view @click="redirect({ url: '/app/pages/setting/index' })"> |
|||
<text class="nc-iconfont nc-icon-shezhiV6xx-1 text-[40rpx] ml-[10rpx]" :style="{ color : diyComponent.textColor }"></text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view v-else class="flex ml-[32rpx] mr-[52rpx] items-center relative" @click="toLogin"> |
|||
<u-avatar src="" size="55"></u-avatar> |
|||
<view class="ml-[22rpx]"> |
|||
<view class="text-[#222222] font-bold text-lg" :style="{ color : diyComponent.textColor }"> |
|||
{{ t('login') }}/{{ t('register') }} |
|||
</view> |
|||
</view> |
|||
<view class="set-icon flex items-center absolute right-0 top-2"> |
|||
<view @click="redirect({ url: '/app/pages/setting/index' })"> |
|||
<text class="nc-iconfont nc-icon-shezhiV6xx-1 text-[40rpx] ml-[10rpx]" :style="{ color : diyComponent.textColor }"></text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="flex m-[30rpx] mb-0 py-[30rpx] items-center"> |
|||
<view class="flex-1 text-center"> |
|||
<view class="font-bold"> |
|||
<view @click="redirect({ url: info ? '/app/pages/member/balance' : '' })" :style="{ color : diyComponent.textColor }">{{ money }}</view> |
|||
</view> |
|||
<view class="text-sm mt-[10rpx]"> |
|||
<view @click="redirect({ url: info ? '/app/pages/member/balance' : '' })" :style="{ color : diyComponent.textColor }">{{ t('balance') }}</view> |
|||
</view> |
|||
</view> |
|||
<view class="border-solid border-white border-l border-b-0 border-t-0 border-r-0 h-[60rpx]"></view> |
|||
<view class="flex-1 text-center"> |
|||
<view class="font-bold"> |
|||
<view @click="redirect({ url: info ? '/app/pages/member/point' : '' })" :style="{ color : diyComponent.textColor }">{{ parseInt(info?.point) || 0 }}</view> |
|||
</view> |
|||
<view class="text-sm mt-[10rpx]"> |
|||
<view @click="redirect({ url: info ? '/app/pages/member/point' : '' })" :style="{ color : diyComponent.textColor }">{{ t('point') }}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- #ifdef MP-WEIXIN --> |
|||
<information-filling ref="infoFill"></information-filling> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed, ref, watch } from 'vue' |
|||
import useMemberStore from '@/stores/member' |
|||
import { useLogin } from '@/hooks/useLogin' |
|||
import { img, isWeixinBrowser, redirect, urlDeconstruction, moneyFormat } from '@/utils/common' |
|||
import { t } from '@/locale' |
|||
import { wechatSync } from '@/app/api/system' |
|||
import useDiyStore from '@/app/stores/diy' |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount','global']); |
|||
|
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
if (diyComponent.value.bgUrl) { |
|||
style += 'background-image:url(' + img(diyComponent.value.bgUrl) + ');'; |
|||
style += 'background-size: 100%;'; |
|||
style += 'background-repeat: no-repeat;'; |
|||
} |
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
|
|||
return style; |
|||
}) |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
|
|||
const memberStore = useMemberStore() |
|||
|
|||
// #ifdef H5 |
|||
const { query } = urlDeconstruction(location.href) |
|||
if (query.code && isWeixinBrowser()) { |
|||
wechatSync({ code: query.code }).then(res => { |
|||
memberStore.getMemberInfo() |
|||
}) |
|||
} |
|||
// #endif |
|||
|
|||
const info = computed(() => { |
|||
// 装修模式 |
|||
if (diyStore.mode == 'decorate') { |
|||
return { |
|||
headimg: '', |
|||
nickname: '昵称', |
|||
balance: 0, |
|||
point: 0, |
|||
money: 0, |
|||
member_no: 'NIU0000021' |
|||
} |
|||
} else { |
|||
return memberStore.info; |
|||
} |
|||
}) |
|||
|
|||
const money = computed(() => { |
|||
if (info.value) { |
|||
let m = parseFloat(info.value.balance) + parseFloat(info.value.money) |
|||
return moneyFormat(m.toString()); |
|||
} else { |
|||
return 0; |
|||
} |
|||
}) |
|||
|
|||
const toLogin = () => { |
|||
useLogin().setLoginBack({ url: '/app/pages/member/index' }) |
|||
} |
|||
|
|||
const infoFill = ref(false) |
|||
const clickAvatar = () => { |
|||
// #ifdef MP-WEIXIN |
|||
infoFill.value.show = true |
|||
// #endif |
|||
|
|||
// #ifdef H5 |
|||
if (isWeixinBrowser()) { |
|||
useLogin().getAuthCode('snsapi_userinfo') |
|||
} else { |
|||
redirect({ url: '/app/pages/member/personal' }) |
|||
} |
|||
// #endif |
|||
} |
|||
let menuButtonInfo = {}; |
|||
// 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API,尚未兼容) |
|||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ |
|||
menuButtonInfo = uni.getMenuButtonBoundingClientRect(); |
|||
// #endif |
|||
|
|||
// 导航栏内部盒子的样式 |
|||
const navbarInnerStyle = computed(() => { |
|||
let style = ''; |
|||
// 导航栏宽度,如果在小程序下,导航栏宽度为胶囊的左边到屏幕左边的距离 |
|||
// #ifdef MP |
|||
if (props.global.topStatusBar.isShow == false) { |
|||
style += 'height:' + menuButtonInfo.height + 'px;'; |
|||
style += 'padding-top:' + menuButtonInfo.top + 'px;'; |
|||
} |
|||
// #endif |
|||
return style; |
|||
}) |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
</style> |
|||
@ -0,0 +1,220 @@ |
|||
<template> |
|||
<view :style="warpCss" class="overflow-hidden" v-if="info && list && list.length"> |
|||
<view v-if="diyComponent.style == 'style-1'" class="rounded-t-[16rpx] flex items-center justify-between style-bg-1 py-[20rpx] px-[30rpx]"> |
|||
<view class="flex items-end"> |
|||
<image :src="img('static/resource/images/diy/member/VIP_02.png')" mode="aspectFit" class="w-[50rpx] h-[36rpx]" /> |
|||
<text class="text-[28rpx] text-[#FFDAA8] ml-[10rpx] font-bold max-w-[440rpx] truncate">{{info.member_level_name}}</text> |
|||
</view> |
|||
<view class="flex items-center justify-center rounded-[30rpx] box-border style-btn w-[120rpx] h-[50rpx]" @click="toLink('/app/pages/member/level')"> |
|||
<text class="text-[24rpx] text-[#333]">{{ info.member_level ? (upgradeGrowth > 0 ? '去升级' : '去查看') : '去解锁' }}</text> |
|||
<text class="iconfont iconxiayibu1 ml-[2rpx] !text-[20rpx] text-[#333]"></text> |
|||
</view> |
|||
</view> |
|||
<view v-if="diyComponent.style == 'style-2'" class="rounded-[16rpx] flex items-center justify-between style-bg-2 p-[30rpx]"> |
|||
<view class="flex flex-col"> |
|||
<view class="flex items-center"> |
|||
<image :src="img('static/resource/images/diy/member/VIP_01.png')" mode="aspectFit" class="w-[74rpx] h-[30rpx]" /> |
|||
<text class="text-[32rpx] text-[#FFE3B1] leading-[normal] ml-[14rpx] font-bold max-w-[420rpx] truncate">{{info.member_level_name}}</text> |
|||
</view> |
|||
<text class="text-[#fff] text-[24rpx] mt-[10rpx] leading-[32rpx]" v-if="benefits_arr && benefits_arr.length">{{info.member_level_name}}购物享{{benefits_arr[0].title}}</text> |
|||
</view> |
|||
<view class="flex items-center justify-center rounded-[30rpx] box-border style-btn w-[120rpx] h-[50rpx]" @click="toLink('/app/pages/member/level')"> |
|||
<text class="text-[24rpx] text-[#333]">{{ info.member_level ? (upgradeGrowth > 0 ? '去升级' : '去查看') : '去解锁' }}</text> |
|||
<text class="iconfont iconxiayibu1 ml-[2rpx] !text-[20rpx] text-[#333]"></text> |
|||
</view> |
|||
</view> |
|||
<view v-if="diyComponent.style == 'style-3'" class="rounded-[16rpx] style-bg-3 p-[30rpx]"> |
|||
<view class="flex items-center justify-between style-border-3 mb-[22rpx] pb-[22rpx]"> |
|||
<view class="flex flex-col"> |
|||
<view class="flex items-center"> |
|||
<view class="flex justify-end leading-[30rpx] box-border text-[#fff] pr-[10rpx] text-[26rpx] w-[120rpx] h-[30rpx] bg-contain bg-no-repeat" :style="{'backgroundImage': 'url('+img('static/resource/images/diy/member/VIP.png')+')'}"> |
|||
VIP.{{currIndex}} |
|||
</view> |
|||
<text class="text-[#733F02] ml-[8rpx] text-[30rpx] font-bold max-w-[380rpx] truncate">{{info.member_level_name}}</text> |
|||
</view> |
|||
<text class="text-[28rpx] text-[#794200] mt-[16rpx]">购物或邀请好友可以提升等级</text> |
|||
</view> |
|||
<view class="flex items-center" @click="toLink('/app/pages/member/level')"> |
|||
<view class="flex flex-col items-center justify-center"> |
|||
<image :src="img('static/resource/images/diy/member/rule.png')" mode="aspectFit" class="w-[26rpx] h-[26rpx]" /> |
|||
<text class="text-[24rpx] text-[#733F02] mt-[10rpx]">规则</text> |
|||
</view> |
|||
<view class="ml-[10rpx] text-[#733F02] !text-[26rpx] nc-iconfont nc-icon-youV6xx"></view> |
|||
</view> |
|||
</view> |
|||
<view class="flex items-center justify-between"> |
|||
<view class="flex flex-col flex-1"> |
|||
<view class="overflow-hidden rounded-[20rpx]"> |
|||
<progress :percent="progress()" activeColor="#fff" backgroundColor="rgba(255,5,5,.1)" stroke-width="6" /> |
|||
</view> |
|||
<text class="text-[24rpx] text-[#794200] mt-[16rpx]" v-if="upgradeGrowth > 0">还差{{upgradeGrowth}}成长值即可升级为{{ list[afterCurrIndex].level_name }}</text> |
|||
<text class="text-[24rpx] text-[#794200] mt-[16rpx]" v-else>恭喜您升级为最高等级</text> |
|||
</view> |
|||
<view class="flex items-center rounded-[30rpx] bg-[rgb(245,230,185)] p-[16rpx] ml-[40rpx]" @click="toLink('/app/pages/member/level')"> |
|||
<text class="text-[28rpx] text-[#733F02]">{{info.member_level ? (upgradeGrowth > 0 ? '做任务' : '点击查看') : '去解锁'}}</text> |
|||
<image :src="img('static/resource/images/diy/member/vector.png')" mode="aspectFit" class="ml-[8rpx] w-[12rpx] h-[18rpx]" /> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed, ref, watch } from 'vue' |
|||
import { img, redirect } from '@/utils/common' |
|||
import useMemberStore from '@/stores/member' |
|||
import { t } from '@/locale' |
|||
import { getMemberLevel } from '@/app/api/member'; |
|||
import useDiyStore from '@/app/stores/diy' |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
const diyStore = useDiyStore(); |
|||
const memberStore = useMemberStore() |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
|
|||
// 获取会员等级列表 |
|||
let list:any = ref([]) |
|||
let upgradeGrowth = ref(0) // 升级下级会员所需的成长值 |
|||
let currIndex = ref(0) //当前会员索引 |
|||
let afterCurrIndex = ref(-1) // 下一个会员等级索引 |
|||
let benefits_arr:any = ref([]) //当前会员权益 |
|||
|
|||
const info:any = computed(() => { |
|||
// 装修模式 |
|||
if (diyStore.mode == 'decorate') { |
|||
upgradeGrowth.value = 0; |
|||
benefits_arr.value = [{'title': '商品包邮'}]; |
|||
currIndex.value = 1; |
|||
list.value = [{}]; |
|||
|
|||
return { |
|||
member_level_name: '会员等级', |
|||
growth: 5 |
|||
} |
|||
} else { |
|||
if (memberStore.info) { |
|||
getMemberLevelFn(); |
|||
} |
|||
return memberStore.info; |
|||
} |
|||
}) |
|||
|
|||
const getMemberLevelFn = ()=> { |
|||
getMemberLevel().then((res: any) => { |
|||
list.value = res.data; |
|||
if(!list.value.length) return false; |
|||
|
|||
let isSet = false; |
|||
|
|||
// 刚进来处理会员等级数据 |
|||
if (info.value && list.value && list.value.length) { |
|||
list.value.forEach((item: any, index: any) => { |
|||
if (item.level_id == info.value.member_level) { |
|||
currIndex.value = index + 1; |
|||
// 会员权益 |
|||
if (item.level_benefits) { |
|||
Object.values(item.level_benefits).forEach((bItem: any) => { |
|||
if (bItem.content) { |
|||
benefits_arr.value.push(bItem.content) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
if (item.growth > info.value.growth && !isSet) { |
|||
afterCurrIndex.value = index; |
|||
isSet = true; |
|||
} |
|||
}) |
|||
} |
|||
|
|||
if (info.value.member_level) { |
|||
if(afterCurrIndex.value == -1){ |
|||
afterCurrIndex.value = list.value.length - 1; |
|||
} |
|||
|
|||
if (list.value[afterCurrIndex.value] && list.value[afterCurrIndex.value].growth) { |
|||
upgradeGrowth.value = list.value[afterCurrIndex.value].growth - info.value.growth; |
|||
} |
|||
}else{ |
|||
// 当前会员没有会员等级,则展示会员等级中的最后一个等级 |
|||
info.value.member_level_name = list.value[0].level_name; |
|||
upgradeGrowth.value = list.value[0].growth; |
|||
afterCurrIndex.value = 0; |
|||
currIndex.value = 1; |
|||
} |
|||
|
|||
}) |
|||
} |
|||
|
|||
// 进度条值 |
|||
let progress = () => { |
|||
let num = 100 |
|||
if(list.value[afterCurrIndex.value] && list.value[afterCurrIndex.value].growth) { |
|||
if(info.value.growth) { |
|||
num = info.value.growth / list.value[afterCurrIndex.value].growth * 100 |
|||
}else{ |
|||
num = 0 |
|||
} |
|||
} |
|||
return num |
|||
} |
|||
|
|||
// 跳转链接 |
|||
const toLink = (link: string)=>{ |
|||
if (diyStore.mode == 'decorate') return false; |
|||
redirect({ url: link }) |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.style-bg-1{ |
|||
background: linear-gradient(to right, #1F1313, #4D4646); |
|||
} |
|||
.style-btn{ |
|||
background: linear-gradient(to right, #FFEACB, #FFD195); |
|||
} |
|||
.style-bg-2{ |
|||
background: linear-gradient(to right, #484846, #222222); |
|||
border-bottom-left-radius: 320rpx 16rpx; |
|||
border-bottom-right-radius: 320rpx 16rpx; |
|||
} |
|||
.style-bg-3{ |
|||
background: linear-gradient(to right, #FFE6C2, #E39F42); |
|||
} |
|||
.style-border-3{ |
|||
position: relative; |
|||
&:after{ |
|||
content: ""; |
|||
position: absolute; |
|||
background: linear-gradient(to right, #F0D2A9, #DBA051); |
|||
height: 2rpx; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,291 @@ |
|||
<template> |
|||
<view :style="warpCss"> |
|||
<view :style="maskLayer"></view> |
|||
<view class="diy-notice relative overflow-hidden"> |
|||
<view class="flex items-center pl-[28rpx] p-[22rpx]"> |
|||
<template v-if="diyComponent.noticeType == 'img'"> |
|||
<template v-if="diyComponent.imgType == 'system'"> |
|||
<image v-if="diyComponent.systemUrl == 'style_1'" :src="img(`static/resource/images/diy/notice/${diyComponent.systemUrl}.png`)" class="h-[40rpx] max-w-[130rpx] mr-[20rpx] flex-shrink-0" mode="heightFix"/> |
|||
<image v-else-if="diyComponent.systemUrl == 'style_2'" :src="img(`static/resource/images/diy/notice/${diyComponent.systemUrl}.png`)" class="w-[200rpx] mr-[20rpx] h-[40rpx] flex-shrink-0" mode="heightFix" /> |
|||
</template> |
|||
<image v-else-if="diyComponent.imgType == 'diy'" :src="img(diyComponent.imageUrl || '')" class="w-[50rpx] h-[50rpx] mr-[20rpx] flex-shrink-0" mode="heightFix"/> |
|||
</template> |
|||
<view v-if="diyComponent.noticeType == 'text' && diyComponent.noticeTitle" class="max-w-[128rpx] px-[12rpx] text-[26rpx] h-[40rpx] leading-[40rpx] text-[var(--primary-color)] bg-[var(--primary-color-light)] truncate rounded-[8rpx] mr-[20rpx] flex-shrink-0">{{ diyComponent.noticeTitle }}</view> |
|||
<view class="flex-1 flex overflow-hidden horizontal-body" id="horizontal-body" :class="{'items-center':diyComponent.scrollWay == 'upDown'}"> |
|||
<!-- 横向滚动 --> |
|||
<view class="horizontal-wrap" :style="marqueeStyle" v-if="diyComponent.scrollWay == 'horizontal'"> |
|||
<view class="marquee marquee-one" id="marquee-one" > |
|||
<view class="item flex-shrink-0 !leading-[40rpx] h-[40rpx]" :class="{'ml-[80rpx]':index}" v-for="(item, index) in diyComponent.list" :key="index" @click="toRedirect(item)" :style="{ color: diyComponent.textColor, fontSize: diyComponent.fontSize * 2 + 'rpx', fontWeight: diyComponent.fontWeight }">{{ item.text }}</view> |
|||
</view> |
|||
<view class="marquee"> |
|||
<view class="item flex-shrink-0 !leading-[40rpx] h-[40rpx]" :class="{'ml-[80rpx]':index}" v-for="(item, index) in diyComponent.list" :key="index" @click="toRedirect(item)" :style="{ color: diyComponent.textColor, fontSize: diyComponent.fontSize * 2 + 'rpx', fontWeight: diyComponent.fontWeight }">{{ item.text }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 上下滚动 --> |
|||
<template v-if="diyComponent.scrollWay == 'upDown'"> |
|||
<swiper :vertical="true" :duration="500" autoplay="true" circular="true" class="flex-1"> |
|||
<swiper-item v-for="(item, index) in diyComponent.list" :key="index" @touchmove.prevent.stop> |
|||
<text @click="toRedirect(item)" class="beyond-hiding using-hidden" :style="{ color: diyComponent.textColor, fontSize: diyComponent.fontSize * 2 + 'rpx', fontWeight: diyComponent.fontWeight }"> |
|||
{{ item.text }} |
|||
</text> |
|||
</swiper-item> |
|||
</swiper> |
|||
<text class="nc-iconfont nc-icon-youV6xx text-[26rpx] -ml-[8rpx] pl-[30rpx]" :style="{'color': '#999', 'fontWeight': diyComponent.fontWeight}"></text> |
|||
|
|||
</template> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<u-popup :show="noticeShow" @close="noticeShow = false" mode="center" :round="5" :safeAreaInsetBottom="false"> |
|||
<view @touchmove.prevent.stop> |
|||
<view class="py-[25rpx] text-sm leading-none border-0 border-solid border-b-[2rpx] border-[#eee] flex items-center justify-between"> |
|||
<text class="ml-[30rpx]">公告</text> |
|||
<text class="mr-[20rpx] nc-iconfont nc-icon-guanbiV6xx text-[35rpx]" @click="noticeShow = false"></text> |
|||
</view> |
|||
<scroll-view scroll-y="true" class="px-6 py-3 w-[480rpx] h-[500rpx] text-sm">{{ noticeContent }}</scroll-view> |
|||
<button @click="noticeShow = false" class="!mx-[30rpx] !mb-[40rpx] !w-auto !h-[70rpx] text-[24rpx] leading-[70rpx] rounded-full text-white !bg-[#ff4500] !text-[#fff]">我知道了</button> |
|||
</view> |
|||
</u-popup> |
|||
|
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// 公告组件 |
|||
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue'; |
|||
import { img } from '@/utils/common'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
const diyStore = useDiyStore(); |
|||
const noticeShow = ref(false); |
|||
const noticeContent = ref(''); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
style += 'position:relative;'; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
|
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`; |
|||
style += 'background-size: cover;background-repeat: no-repeat;'; |
|||
} |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
// 背景图加遮罩层 |
|||
const maskLayer = computed(()=>{ |
|||
var style = ''; |
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += 'position:absolute;top:0;width:100%;'; |
|||
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`; |
|||
style += `height:${height.value}px;`; |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
} |
|||
|
|||
return style; |
|||
}); |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
|
|||
const marqueeBodyWidth = ref(0); // 容器宽度 |
|||
const marqueeStyle = ref(''); // 横向滚动样式 |
|||
const time = ref(0); // 滚动完成时间 |
|||
const delayTime = ref(800); // 动画延迟时间 |
|||
// px转rpx |
|||
const pxToRpx=(px)=> { |
|||
const screenWidth = uni.getSystemInfoSync().screenWidth |
|||
return (750 * Number.parseInt(px)) / screenWidth |
|||
} |
|||
// 绑定横向滚动事件 |
|||
const bindCrossSlipEvent = ()=> { |
|||
if (diyComponent.value.scrollWay == 'horizontal') { |
|||
setTimeout(() => { |
|||
nextTick(() => { |
|||
// #ifdef MP-WEIXIN |
|||
uni.createSelectorQuery().in(instance).select('.horizontal-body').boundingClientRect(res => { |
|||
marqueeBodyWidth.value = res.width; |
|||
console.log(pxToRpx(res.width)) |
|||
const query = uni.createSelectorQuery().in(instance); |
|||
query.selectAll('.marquee-one').boundingClientRect((data:any) => { |
|||
let width = data[0].width |
|||
time.value = pxToRpx(width) * 10; |
|||
if (marqueeBodyWidth.value > width) { |
|||
marqueeStyle.value = `animation: none;`; |
|||
} else { |
|||
marqueeStyle.value = ` |
|||
animation-duration: ${time.value}ms; |
|||
animation-delay: ${delayTime.value}ms;`; |
|||
} |
|||
}).exec(); |
|||
}).exec(); |
|||
// #endif |
|||
// #ifdef H5 |
|||
marqueeBodyWidth.value = window.document.getElementById('horizontal-body').offsetWidth; |
|||
const width = window.document.getElementById('marquee-one').offsetWidth; |
|||
time.value = pxToRpx(width) * 10; |
|||
if (marqueeBodyWidth.value > width) { |
|||
marqueeStyle.value = `animation: none;`; |
|||
} else { |
|||
marqueeStyle.value = ` |
|||
animation-duration: ${time.value}ms; |
|||
animation-delay: ${delayTime.value}ms;`; |
|||
} |
|||
// #endif |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
onMounted(() => { |
|||
bindCrossSlipEvent(); |
|||
refresh(); |
|||
// 装修模式下刷新 |
|||
if (diyStore.mode == 'decorate') { |
|||
watch( |
|||
() => diyComponent.value, |
|||
(newValue, oldValue) => { |
|||
if (newValue && newValue.componentName == 'Notice') { |
|||
bindCrossSlipEvent(); |
|||
refresh(); |
|||
} |
|||
} |
|||
) |
|||
} |
|||
}); |
|||
|
|||
const instance = getCurrentInstance(); |
|||
const height = ref(0) |
|||
|
|||
const refresh = ()=> { |
|||
nextTick(() => { |
|||
const query = uni.createSelectorQuery().in(instance); |
|||
query.select('.diy-notice').boundingClientRect((data: any) => { |
|||
height.value = data.height; |
|||
}).exec(); |
|||
}) |
|||
} |
|||
|
|||
const toRedirect = (data: {}) => { |
|||
if (diyStore.mode == 'decorate') return false; |
|||
|
|||
if (diyComponent.value.showType == 'popup') { |
|||
noticeShow.value = true; |
|||
noticeContent.value = data.text; |
|||
} else { |
|||
diyStore.toRedirect(data); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.main-wrap { |
|||
display: inline-block; |
|||
width: calc(100% - 100rpx); |
|||
position: relative; |
|||
} |
|||
|
|||
swiper { |
|||
height: 50rpx; |
|||
} |
|||
|
|||
.beyond-hiding { |
|||
display: inline-block; |
|||
width: 100%; |
|||
white-space: nowrap; |
|||
line-height: 50rpx |
|||
} |
|||
|
|||
.notice-popup { |
|||
padding: 0 30rpx 40rpx; |
|||
background-color: #fff; |
|||
|
|||
.head-wrap { |
|||
font-size: 32rpx; |
|||
line-height: 100rpx; |
|||
height: 100rpx; |
|||
display: block; |
|||
text-align: center; |
|||
position: relative; |
|||
border-bottom: 2rpx solid #eeeeee; |
|||
margin-bottom: 20rpx; |
|||
|
|||
.iconfont { |
|||
position: absolute; |
|||
float: right; |
|||
right: 0; |
|||
font-size: 32rpx; |
|||
} |
|||
} |
|||
|
|||
.content-wrap { |
|||
max-height: 600rpx; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
button { |
|||
margin-top: 40rpx; |
|||
} |
|||
} |
|||
|
|||
.horizontal-wrap { |
|||
height: 40rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
transform: translateZ(0); |
|||
animation: marquee 0s 0s linear infinite; |
|||
} |
|||
|
|||
|
|||
|
|||
.marquee { |
|||
display: flex; |
|||
align-items: center; |
|||
height: 100%; |
|||
white-space: nowrap; |
|||
padding-right: 60rpx; |
|||
// backface-visibility: hidden; |
|||
// -webkit-perspective: 1000; |
|||
// -moz-perspective: 1000; |
|||
// -ms-perspective: 1000; |
|||
// perspective: 1000; |
|||
} |
|||
|
|||
@keyframes marquee { |
|||
0% { |
|||
transform: translateX(0); |
|||
} |
|||
|
|||
100% { |
|||
transform: translateX(-50%); |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,125 @@ |
|||
<template> |
|||
<view :style="warpCss"> |
|||
<view :style="maskLayer"></view> |
|||
<view class="diy-rich-text relative"> |
|||
<view v-if="diyComponent.html && diyComponent.html != '<p><br></p>'"> |
|||
<u-parse :content="diyComponent.html" :tagStyle="{img: 'vertical-align: top;'}"></u-parse> |
|||
</view> |
|||
<template v-else> |
|||
<view>点此编辑『富文本』内容 ——></view> |
|||
<view> |
|||
<text>你可以对文字进行</text> |
|||
<text>、</text> |
|||
<text class="font-bold">加粗</text> |
|||
<text>、</text> |
|||
<text class="italic">斜体</text> |
|||
<text>、</text> |
|||
<text class="underline">下划线</text> |
|||
<text>、</text> |
|||
<text class="line-through">删除线</text> |
|||
<text>、文字</text> |
|||
<text style="color: rgb(0, 176, 240);">颜色</text> |
|||
<text>、</text> |
|||
<text style="background-color: rgb(255, 192, 0); color: rgb(255, 255, 255);">背景色</text> |
|||
<text>、以及字号</text> |
|||
<text class="text-lg">大</text> |
|||
<text class="text-sm">小</text> |
|||
<text class="pl-[10rpx]">等简单排版操作。</text> |
|||
</view> |
|||
<view>也可在这里插入图片、并对图片加上超级链接,方便用户点击。</view> |
|||
</template> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// 富文本组件 |
|||
import { ref,computed, watch, onMounted, nextTick,getCurrentInstance } from 'vue'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
import { img } from '@/utils/common'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
|
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
style += 'position:relative;'; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
|
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`; |
|||
style += 'background-size: cover;background-repeat: no-repeat;'; |
|||
} |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
// 背景图加遮罩层 |
|||
const maskLayer = computed(()=>{ |
|||
var style = ''; |
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += 'position:absolute;top:0;width:100%;'; |
|||
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`; |
|||
style += `height:${height.value}px;`; |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
} |
|||
|
|||
return style; |
|||
}); |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
|
|||
onMounted(() => { |
|||
refresh(); |
|||
// 装修模式下刷新 |
|||
if (diyStore.mode == 'decorate') { |
|||
watch( |
|||
() => diyComponent.value, |
|||
(newValue, oldValue) => { |
|||
if (newValue && newValue.componentName == 'RichText') { |
|||
refresh(); |
|||
} |
|||
} |
|||
) |
|||
} |
|||
}); |
|||
|
|||
const instance = getCurrentInstance(); |
|||
const height = ref(0) |
|||
|
|||
const refresh = ()=> { |
|||
nextTick(() => { |
|||
const query = uni.createSelectorQuery().in(instance); |
|||
query.select('.diy-rich-text').boundingClientRect((data: any) => { |
|||
height.value = data.height; |
|||
}).exec(); |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<style></style> |
|||
@ -0,0 +1,590 @@ |
|||
<template> |
|||
<view :style="warpCss"> |
|||
<view :style="maskLayer"></view> |
|||
<view :class="['rubik-cube relative', diyStore.mode]"> |
|||
|
|||
<!-- 1左2右 --> |
|||
<template v-if="diyComponent.mode == 'row1-lt-of2-rt'"> |
|||
<view class="template-left"> |
|||
<view @click="diyStore.toRedirect(diyComponent.list[0].link)" :class="['item', diyComponent.mode]" |
|||
:style="{ marginRight: diyComponent.imageGap * 2 + 'rpx', width: diyComponent.list[0].imgWidth, height: diyComponent.list[0].imgHeight + 'px' }"> |
|||
<image v-if="diyComponent.list[0].imageUrl" :src="img(diyComponent.list[0].imageUrl)" mode="scaleToFill" :style="diyComponent.list[0].pageItemStyle" :show-menu-by-longpress="true"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" :style="diyComponent.list[0].pageItemStyle" :show-menu-by-longpress="true"/> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="template-right"> |
|||
<template v-for="(item, index) in diyComponent.list" :key="index"> |
|||
<template v-if="index > 0"> |
|||
<view @click="diyStore.toRedirect(item.link)" :class="['item', diyComponent.mode]" |
|||
:style="{ marginBottom: diyComponent.imageGap * 2 + 'rpx', width: item.imgWidth, height: item.imgHeight + 'px' }"> |
|||
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="scaleToFill" :style="item.pageItemStyle" :show-menu-by-longpress="true"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" :style="item.pageItemStyle" :show-menu-by-longpress="true"/> |
|||
</view> |
|||
</template> |
|||
</template> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 1左3右 --> |
|||
<template v-else-if="diyComponent.mode == 'row1-lt-of1-tp-of2-bm'"> |
|||
<view class="template-left"> |
|||
<view @click="diyStore.toRedirect(diyComponent.list[0].link)" :class="['item', diyComponent.mode]" |
|||
:style="{ marginRight: diyComponent.imageGap * 2 + 'rpx', width: diyComponent.list[0].imgWidth, height: diyComponent.list[0].imgHeight + 'px' }"> |
|||
<image v-if="diyComponent.list[0].imageUrl" :src="img(diyComponent.list[0].imageUrl)" mode="scaleToFill" :style="diyComponent.list[0].pageItemStyle" :show-menu-by-longpress="true"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" :style="diyComponent.list[0].pageItemStyle" :show-menu-by-longpress="true"/> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="template-right"> |
|||
<view @click="diyStore.toRedirect(diyComponent.list[1].link)" :class="['item', diyComponent.mode]" |
|||
:style="{ marginBottom: diyComponent.imageGap * 2 + 'rpx', width: diyComponent.list[1].imgWidth, height: diyComponent.list[1].imgHeight + 'px' }"> |
|||
<image v-if="diyComponent.list[1].imageUrl" :src="img(diyComponent.list[1].imageUrl)" mode="scaleToFill" :style="diyComponent.list[1].pageItemStyle" :show-menu-by-longpress="true"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" :style="diyComponent.list[1].pageItemStyle" :show-menu-by-longpress="true"/> |
|||
</view> |
|||
<view class="template-bottom"> |
|||
<template v-for="(item, index) in diyComponent.list" :key="index"> |
|||
<template v-if="index > 1"> |
|||
<view @click="diyStore.toRedirect(item.link)" :class="['item', diyComponent.mode]" :style="{ |
|||
marginRight: diyComponent.imageGap * 2 + 'rpx', |
|||
width: item.imgWidth, |
|||
height: item.imgHeight + 'px' |
|||
}"> |
|||
<image v-if="item.imageUrl" :src="img(item.imageUrl)" mode="scaleToFill" :style="item.pageItemStyle" :show-menu-by-longpress="true"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" mode="scaleToFill" :style="item.pageItemStyle" :show-menu-by-longpress="true"/> |
|||
</view> |
|||
</template> |
|||
</template> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<template v-else> |
|||
<view :class="['item', diyComponent.mode]" v-for="(item, index) in diyComponent.list" :key="index" |
|||
@click="diyStore.toRedirect(item.link)" |
|||
:style="{ marginRight: diyComponent.imageGap * 2 + 'rpx', marginBottom: diyComponent.imageGap * 2 + 'rpx', width: item.widthStyle, height: item.imgHeight + 'px' }"> |
|||
<image v-if="item.imageUrl" :src="img(item.imageUrl)" :mode="'scaleToFill'" :style="item.pageItemStyle" :show-menu-by-longpress="true"/> |
|||
<image v-else :src="img('static/resource/images/diy/figure.png')" :mode="'scaleToFill'" :style="item.pageItemStyle" :show-menu-by-longpress="true"/> |
|||
</view> |
|||
</template> |
|||
|
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// 魔方 |
|||
import { ref,onMounted, computed, watch,nextTick,getCurrentInstance } from 'vue'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
import { img } from '@/utils/common'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
|
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
/** |
|||
* 处理rpx渲染之后变成rem存在小数的问题 |
|||
* @param rpx |
|||
*/ |
|||
const upx2px = (rpx: number) => { |
|||
return uni.upx2px(rpx) + 1 |
|||
} |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
style += 'position:relative;'; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
|
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`; |
|||
style += 'background-size: cover;background-repeat: no-repeat;'; |
|||
} |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
// 背景图加遮罩层 |
|||
const maskLayer = computed(()=>{ |
|||
var style = ''; |
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += 'position:absolute;top:0;width:100%;'; |
|||
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`; |
|||
style += `height:${height.value}px;`; |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
} |
|||
|
|||
return style; |
|||
}); |
|||
|
|||
const countBorderRadius = (type, index) => { |
|||
var obj = ''; |
|||
if (diyComponent.value.elementAngle == 'right') { |
|||
return obj; |
|||
} |
|||
var defaultData:any = { |
|||
'row1-lt-of2-rt': [ |
|||
['border-top-right-radius', 'border-bottom-right-radius'], |
|||
['border-top-left-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'], |
|||
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius'] |
|||
], |
|||
'row1-lt-of1-tp-of2-bm': [ |
|||
['border-top-right-radius', 'border-bottom-right-radius'], |
|||
['border-top-left-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'], |
|||
['border-radius'], |
|||
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius'] |
|||
], |
|||
'row1-tp-of2-bm': [ |
|||
['border-bottom-left-radius', 'border-bottom-right-radius'], |
|||
['border-top-left-radius', 'border-bottom-right-radius', 'border-top-right-radius'], |
|||
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius'] |
|||
], |
|||
'row2-lt-of2-rt': [ |
|||
['border-top-right-radius', 'border-bottom-left-radius', 'border-bottom-right-radius'], |
|||
['border-top-left-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'], |
|||
['border-top-left-radius', 'border-bottom-right-radius', 'border-top-right-radius'], |
|||
['border-top-left-radius', 'border-bottom-left-radius', 'border-top-right-radius'] |
|||
], |
|||
'row1-of4': [ |
|||
['border-top-right-radius', 'border-bottom-right-radius'], |
|||
['border-radius'], |
|||
['border-radius'], |
|||
['border-top-left-radius', 'border-bottom-left-radius'] |
|||
], |
|||
'row1-of3': [ |
|||
['border-top-right-radius', 'border-bottom-right-radius'], |
|||
['border-radius'], |
|||
['border-top-left-radius', 'border-bottom-left-radius'] |
|||
], |
|||
'row1-of2': [ |
|||
['border-top-right-radius', 'border-bottom-right-radius'], |
|||
['border-top-left-radius', 'border-bottom-left-radius'] |
|||
] |
|||
}; |
|||
|
|||
defaultData[type][index].forEach((item, index) => { |
|||
obj += 'border-top-left-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;'; |
|||
obj += 'border-top-right-radius:' + diyComponent.value.topElementRounded * 2 + 'rpx;'; |
|||
obj += 'border-bottom-left-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;'; |
|||
obj += 'border-bottom-right-radius:' + diyComponent.value.bottomElementRounded * 2 + 'rpx;'; |
|||
}); |
|||
return obj; |
|||
} |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
|
|||
onMounted(() => { |
|||
refresh(); |
|||
// 装修模式下刷新 |
|||
if (diyStore.mode == 'decorate') { |
|||
watch( |
|||
() => diyComponent.value, |
|||
(newValue, oldValue) => { |
|||
if (newValue && newValue.componentName == 'RubikCube') { |
|||
refresh(); |
|||
} |
|||
} |
|||
) |
|||
}else{ |
|||
watch( |
|||
() => diyComponent.value, |
|||
(newValue, oldValue) => { |
|||
refresh(); |
|||
} |
|||
) |
|||
} |
|||
}); |
|||
|
|||
const instance = getCurrentInstance(); |
|||
const height = ref(0) |
|||
|
|||
const refresh = () => { |
|||
if (diyStore.mode == 'decorate') { |
|||
diyComponent.value.list.forEach((item : any) => { |
|||
// 装修模式下设置默认图 |
|||
if (item.imageUrl == '') { |
|||
item.imgWidth = 690; |
|||
item.imgHeight = 330; |
|||
} |
|||
}); |
|||
} |
|||
handleData() |
|||
nextTick(() => { |
|||
const query = uni.createSelectorQuery().in(instance); |
|||
query.select('.rubik-cube').boundingClientRect((data: any) => { |
|||
height.value = data.height; |
|||
}).exec(); |
|||
}) |
|||
} |
|||
|
|||
const handleData = () => { |
|||
var singleRow:any = { |
|||
'row1-of2': { |
|||
ratio: 2, |
|||
width: 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)' |
|||
}, |
|||
'row1-of3': { |
|||
ratio: 3, |
|||
width: 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 4) + 'px) / 3)' |
|||
}, |
|||
'row1-of4': { |
|||
ratio: 4, |
|||
width: 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 6) + 'px) / 4)' |
|||
} |
|||
}; |
|||
|
|||
diyComponent.value.list.forEach((item, index) => { |
|||
item.pageItemStyle = countBorderRadius(diyComponent.value.mode, index); |
|||
}); |
|||
|
|||
if (singleRow[diyComponent.value.mode]) { |
|||
calcSingleRow(singleRow[diyComponent.value.mode]); |
|||
} else if (diyComponent.value.mode == 'row2-lt-of2-rt') { |
|||
calcFourSquare(); |
|||
} else if (diyComponent.value.mode == 'row1-lt-of2-rt') { |
|||
calcRowOneLeftOfTwoRight(); |
|||
} else if (diyComponent.value.mode == 'row1-tp-of2-bm') { |
|||
calcRowOneTopOfTwoBottom(); |
|||
} else if (diyComponent.value.mode == 'row1-lt-of1-tp-of2-bm') { |
|||
calcRowOneLeftOfOneTopOfTwoBottom(); |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* 魔方:单行多个,平分宽度 |
|||
* 公式: |
|||
* 宽度:屏幕宽度/2,示例:375/2=187.5 |
|||
* 比例:原图高/原图宽,示例:322/690=0.46 |
|||
* 高度:宽度*比例,示例:187.5*0.46=86.25 |
|||
*/ |
|||
const calcSingleRow = (params:any) => { |
|||
uni.getSystemInfo({ |
|||
success: res => { |
|||
let maxHeight = 0; |
|||
|
|||
diyComponent.value.list.forEach((item, index) => { |
|||
var ratio = item.imgHeight / item.imgWidth; |
|||
|
|||
let width = res.windowWidth - upx2px(diyComponent.value.margin.both * 2); // 减去左右间距 |
|||
if (diyComponent.value.imageGap > 0) { |
|||
width -= upx2px(params.ratio * diyComponent.value.imageGap * 2); // 减去间隙 |
|||
} |
|||
item.imgWidth = width / params.ratio; |
|||
item.imgHeight = item.imgWidth * ratio; |
|||
|
|||
if (maxHeight == 0 || maxHeight < item.imgHeight) maxHeight = item.imgHeight; |
|||
}) |
|||
|
|||
diyComponent.value.list.forEach((item, index) => { |
|||
item.widthStyle = params.width; |
|||
item.imgHeight = maxHeight; |
|||
}); |
|||
} |
|||
}) |
|||
}; |
|||
|
|||
/** |
|||
* 魔方:四方型,各占50% |
|||
*/ |
|||
const calcFourSquare = () => { |
|||
uni.getSystemInfo({ |
|||
success: res => { |
|||
let maxHeightFirst = 0; |
|||
let maxHeightTwo = 0; |
|||
diyComponent.value.list.forEach((item, index) => { |
|||
var ratio = item.imgHeight / item.imgWidth; |
|||
item.imgWidth = res.windowWidth; |
|||
item.imgWidth -= upx2px(diyComponent.value.margin.both * 4); |
|||
if (diyComponent.value.imageGap > 0) { |
|||
item.imgWidth -= upx2px(diyComponent.value.imageGap * 2); |
|||
} |
|||
item.imgWidth = item.imgWidth / 2; |
|||
item.imgHeight = item.imgWidth * ratio; |
|||
|
|||
// 获取每行最大高度 |
|||
if (index <= 1) { |
|||
if (maxHeightFirst == 0 || maxHeightFirst < item.imgHeight) { |
|||
maxHeightFirst = item.imgHeight; |
|||
} |
|||
} else if (index > 1) { |
|||
if (maxHeightTwo == 0 || maxHeightTwo < item.imgHeight) { |
|||
maxHeightTwo = item.imgHeight; |
|||
} |
|||
} |
|||
}); |
|||
diyComponent.value.list.forEach((item, index) => { |
|||
item.imgWidth = 'calc((100% - ' + upx2px(diyComponent.value.imageGap * 2) + 'px) / 2)'; |
|||
item.widthStyle = item.imgWidth; |
|||
if (index <= 1) { |
|||
item.imgHeight = maxHeightFirst; |
|||
} else if (index > 1) { |
|||
item.imgHeight = maxHeightTwo; |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 魔方:1左2右 |
|||
*/ |
|||
const calcRowOneLeftOfTwoRight = () => { |
|||
let rightHeight = 0; // 右侧两图平分高度 |
|||
let divide = 'left'; // 划分规则,left:左,right:右 |
|||
if (diyComponent.value.list[1].imgWidth === diyComponent.value.list[2].imgWidth) divide = 'right'; |
|||
uni.getSystemInfo({ |
|||
success: res => { |
|||
diyComponent.value.list.forEach((item, index) => { |
|||
if (index == 0) { |
|||
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例 |
|||
item.imgWidth = res.windowWidth - upx2px(diyComponent.value.margin.both * 4) - upx2px(diyComponent.value.imageGap * 2); |
|||
item.imgWidth = item.imgWidth / 2; |
|||
item.imgHeight = item.imgWidth * ratio; |
|||
rightHeight = (item.imgHeight - upx2px(diyComponent.value.imageGap * 2)) / 2; |
|||
item.imgWidth += 'px'; |
|||
} else { |
|||
item.imgWidth = diyComponent.value.list[0].imgWidth; |
|||
item.imgHeight = rightHeight; |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 魔方:1上2下 |
|||
*/ |
|||
const calcRowOneTopOfTwoBottom = () => { |
|||
var maxHeight = 0; |
|||
uni.getSystemInfo({ |
|||
success: res => { |
|||
diyComponent.value.list.forEach((item, index) => { |
|||
|
|||
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例 |
|||
if (index == 0) { |
|||
item.imgWidth = res.windowWidth - upx2px(diyComponent.value.margin.both * 4); |
|||
} else if (index > 0) { |
|||
item.imgWidth = res.windowWidth - upx2px(diyComponent.value.margin.both * 4) - upx2px(diyComponent.value.imageGap * 2); |
|||
item.imgWidth = item.imgWidth / 2; |
|||
} |
|||
|
|||
item.imgHeight = item.imgWidth * ratio; |
|||
|
|||
// 获取最大高度 |
|||
if (index > 0 && (maxHeight == 0 || maxHeight < item.imgHeight)) maxHeight = item.imgHeight; |
|||
|
|||
}); |
|||
diyComponent.value.list.forEach((item, index) => { |
|||
item.imgWidth += 'px'; |
|||
item.widthStyle = item.imgWidth; |
|||
if (index > 0) item.imgHeight = maxHeight; |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 魔方:1左3右 |
|||
*/ |
|||
const calcRowOneLeftOfOneTopOfTwoBottom = () => { |
|||
uni.getSystemInfo({ |
|||
success: res => { |
|||
diyComponent.value.list.forEach((item, index) => { |
|||
// 左图 |
|||
if (index == 0) { |
|||
var ratio = item.imgHeight / item.imgWidth; // 获取左图的尺寸比例 |
|||
item.imgWidth = res.windowWidth - upx2px(diyComponent.value.margin.both * 4) - upx2px(diyComponent.value.imageGap * 2); |
|||
item.imgWidth = item.imgWidth / 2; |
|||
item.imgHeight = item.imgWidth * ratio; |
|||
} else if (index == 1) { |
|||
item.imgWidth = diyComponent.value.list[0].imgWidth; |
|||
item.imgHeight = (diyComponent.value.list[0].imgHeight - upx2px(diyComponent.value.imageGap * 2)) / 2; |
|||
} else if (index > 1) { |
|||
item.imgWidth = (diyComponent.value.list[0].imgWidth - upx2px(diyComponent.value.imageGap * 2)) / 2; |
|||
item.imgHeight = diyComponent.value.list[1].imgHeight; |
|||
} |
|||
}); |
|||
|
|||
diyComponent.value.list.forEach((item, index) => { |
|||
item.imgWidth += 'px'; |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.rubik-cube { |
|||
overflow: hidden; |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
justify-content: space-between; |
|||
|
|||
.item { |
|||
text-align: center; |
|||
line-height: 0; |
|||
overflow: hidden; |
|||
|
|||
image { |
|||
width: 100%; |
|||
max-width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 一行两个 |
|||
.rubik-cube .item.row1-of2 { |
|||
box-sizing: border-box; |
|||
margin-top: 0 !important; |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row1-of2:nth-child(1) { |
|||
margin-left: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row1-of2:nth-child(2) { |
|||
margin-right: 0 !important; |
|||
} |
|||
|
|||
// 一行三个 |
|||
.rubik-cube .item.row1-of3 { |
|||
box-sizing: border-box; |
|||
margin-top: 0 !important; |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row1-of3:nth-child(1) { |
|||
margin-left: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row1-of3:nth-child(3) { |
|||
margin-right: 0 !important; |
|||
} |
|||
|
|||
// 一行四个 |
|||
.rubik-cube .item.row1-of4 { |
|||
box-sizing: border-box; |
|||
margin-top: 0 !important; |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row1-of4:nth-child(1) { |
|||
margin-left: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row1-of4:nth-child(4) { |
|||
margin-right: 0 !important; |
|||
} |
|||
|
|||
// 两左两右 |
|||
.rubik-cube .item.row2-lt-of2-rt { |
|||
// width: 50%; |
|||
display: inline-block; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.rubik-cube .item.row2-lt-of2-rt:nth-child(1) { |
|||
margin-left: 0 !important; |
|||
margin-top: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row2-lt-of2-rt:nth-child(2) { |
|||
margin-right: 0 !important; |
|||
margin-top: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row2-lt-of2-rt:nth-child(3) { |
|||
margin-left: 0 !important; |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row2-lt-of2-rt:nth-child(4) { |
|||
margin-right: 0 !important; |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
// 一左两右 |
|||
.rubik-cube .template-left, |
|||
.rubik-cube .template-right { |
|||
// width: 50%; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.rubik-cube .template-left .item.row1-lt-of2-rt:nth-child(1) { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.rubik-cube .template-right .item.row1-lt-of2-rt:nth-child(2) { |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube.row1-lt-of2-rt .template-right { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
// 一上两下 |
|||
.rubik-cube .item.row1-tp-of2-bm:nth-child(1) { |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
margin-top: 0 !important; |
|||
margin-left: 0 !important; |
|||
margin-right: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row1-tp-of2-bm:nth-child(2) { |
|||
// width: 50%; |
|||
box-sizing: border-box; |
|||
margin-left: 0 !important; |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
.rubik-cube .item.row1-tp-of2-bm:nth-child(3) { |
|||
// width: 50%; |
|||
box-sizing: border-box; |
|||
margin-right: 0 !important; |
|||
margin-bottom: 0 !important; |
|||
} |
|||
|
|||
// 一左三右 |
|||
.rubik-cube .template-left .item.row1-lt-of1-tp-of2-bm { |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.rubik-cube .template-bottom { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.rubik-cube .template-bottom .item:nth-child(2) { |
|||
margin-right: 0 !important; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,129 @@ |
|||
<template> |
|||
<view :style="warpCss"> |
|||
<view :style="maskLayer"></view> |
|||
<view class="diy-text relative"> |
|||
<view v-if="diyComponent.style == 'style-1'" class=" px-[20rpx]"> |
|||
<view @click="diyStore.toRedirect(diyComponent.link)"> |
|||
<view :style="{ |
|||
fontSize: diyComponent.fontSize * 2 + 'rpx', |
|||
color: diyComponent.textColor, |
|||
fontWeight: diyComponent.fontWeight, |
|||
textAlign : diyComponent.textAlign |
|||
}"> |
|||
{{ diyComponent.text }} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view v-if="diyComponent.style == 'style-2'" class=" px-[20rpx] flex items-center"> |
|||
<view @click="diyStore.toRedirect(diyComponent.link)"> |
|||
<view class="max-w-[200rpx] truncate" :style="{ |
|||
fontSize: diyComponent.fontSize * 2 + 'rpx', |
|||
color: diyComponent.textColor, |
|||
fontWeight: diyComponent.fontWeight |
|||
}"> |
|||
{{ diyComponent.text }} |
|||
</view> |
|||
</view> |
|||
<text class="ml-[16rpx] max-w-[300rpx] truncate" :style="{ color: diyComponent.subTitle.color, fontSize: diyComponent.subTitle.fontSize * 2 + 'rpx', }">{{ diyComponent.subTitle.text }}</text> |
|||
<view class="ml-auto text-right " v-if="diyComponent.more.isShow" :style="{ color: diyComponent.more.color }"> |
|||
<view @click="diyStore.toRedirect(diyComponent.more.link)" class="flex items-center"> |
|||
<text class="max-w-[200rpx] truncate text-[24rpx]">{{ diyComponent.more.text }}</text> |
|||
<text class="nc-iconfont nc-icon-youV6xx text-[26rpx]" :style="{ color: diyComponent.more.color }"></text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
// 标题 |
|||
import { ref, computed, watch,onMounted,nextTick,getCurrentInstance } from 'vue'; |
|||
import useDiyStore from '@/app/stores/diy'; |
|||
import { img } from '@/utils/common'; |
|||
|
|||
const props = defineProps(['component', 'index', 'pullDownRefreshCount']); |
|||
const diyStore = useDiyStore(); |
|||
|
|||
const diyComponent = computed(() => { |
|||
if (diyStore.mode == 'decorate') { |
|||
return diyStore.value[props.index]; |
|||
} else { |
|||
return props.component; |
|||
} |
|||
}) |
|||
|
|||
const warpCss = computed(() => { |
|||
var style = ''; |
|||
style += 'position:relative;'; |
|||
if(diyComponent.value.componentStartBgColor) { |
|||
if (diyComponent.value.componentStartBgColor && diyComponent.value.componentEndBgColor) style += `background:linear-gradient(${diyComponent.value.componentGradientAngle},${diyComponent.value.componentStartBgColor},${diyComponent.value.componentEndBgColor});`; |
|||
else style += 'background-color:' + diyComponent.value.componentStartBgColor + ';'; |
|||
} |
|||
|
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += `background-image:url('${ img(diyComponent.value.componentBgUrl) }');`; |
|||
style += 'background-size: cover;background-repeat: no-repeat;'; |
|||
} |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
return style; |
|||
}) |
|||
|
|||
// 背景图加遮罩层 |
|||
const maskLayer = computed(()=>{ |
|||
var style = ''; |
|||
if(diyComponent.value.componentBgUrl) { |
|||
style += 'position:absolute;top:0;width:100%;'; |
|||
style += `background: rgba(0,0,0,${diyComponent.value.componentBgAlpha / 10});`; |
|||
style += `height:${height.value}px;`; |
|||
|
|||
if (diyComponent.value.topRounded) style += 'border-top-left-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.topRounded) style += 'border-top-right-radius:' + diyComponent.value.topRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-left-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
if (diyComponent.value.bottomRounded) style += 'border-bottom-right-radius:' + diyComponent.value.bottomRounded * 2 + 'rpx;'; |
|||
} |
|||
|
|||
return style; |
|||
}); |
|||
|
|||
watch( |
|||
() => props.pullDownRefreshCount, |
|||
(newValue, oldValue) => { |
|||
// 处理下拉刷新业务 |
|||
} |
|||
) |
|||
|
|||
onMounted(() => { |
|||
refresh(); |
|||
// 装修模式下刷新 |
|||
if (diyStore.mode == 'decorate') { |
|||
watch( |
|||
() => diyComponent.value, |
|||
(newValue, oldValue) => { |
|||
if (newValue && newValue.componentName == 'Text') { |
|||
refresh(); |
|||
} |
|||
} |
|||
) |
|||
} |
|||
}); |
|||
|
|||
const instance = getCurrentInstance(); |
|||
const height = ref(0) |
|||
|
|||
const refresh = ()=> { |
|||
nextTick(() => { |
|||
const query = uni.createSelectorQuery().in(instance); |
|||
query.select('.diy-text').boundingClientRect((data: any) => { |
|||
height.value = data.height; |
|||
}).exec(); |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
</style> |
|||
@ -0,0 +1,15 @@ |
|||
<template> |
|||
<view> |
|||
固定模板示例,我也可以装修 |
|||
<!-- 自定义模板渲染 --> |
|||
<diy-group :data="props.data" :pullDownRefreshCount="props.pullDownRefreshCount"></diy-group> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { computed, watch } from 'vue'; |
|||
import diyGroup from '@/addon/components/diy/group/index.vue' |
|||
const props = defineProps(['data', 'pullDownRefreshCount']); |
|||
</script> |
|||
|
|||
<style></style> |
|||
@ -0,0 +1,37 @@ |
|||
<template> |
|||
<view class="tags" :style="{ '--backColor': backColor }"> |
|||
{{ text }} |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
interface propInt { |
|||
text: string |
|||
backColor?: string |
|||
} |
|||
|
|||
withDefaults(defineProps<propInt>(), { |
|||
text: '', |
|||
backColor: '#4b81fb' |
|||
}) |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.tags { |
|||
position: relative; |
|||
margin: 10rpx 0 20rpx; |
|||
padding-left: 26rpx; |
|||
font-size: 28rpx; |
|||
color: #28211f; |
|||
|
|||
&::before { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 8rpx; |
|||
height: 100%; |
|||
background: var(--back-color) !important; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"personalSettings": "Personal settings", |
|||
"switchLang": "Switch language", |
|||
"version": "Version", |
|||
"logout": "Log out" |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"detail": "文章详情", |
|||
"abstract": "摘要", |
|||
"loadingText": "正在加载" |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"list": "文章列表", |
|||
"noData": "~ 暂无数据 ~", |
|||
"all": "全部", |
|||
"end": "-- 到底了 --", |
|||
"searchPlaceholder": "请输入搜索关键词" |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
{ |
|||
"bindMobile": "绑定手机号", |
|||
"bind": "绑定", |
|||
"binding": "绑定中", |
|||
"agreeTips": "请阅读并同意", |
|||
"pleaceAgree": "请勾选已阅读并同意", |
|||
"mobilePlaceholder": "请输入手机号", |
|||
"codePlaceholder": "请输入验证码", |
|||
"weixinUserAuth": "微信用户一键绑定" |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
{ |
|||
"logining": "登录中", |
|||
"usernamePlaceholder": "请输入账号", |
|||
"passwordPlaceholder": "请输入密码", |
|||
"resetpwd": "忘记密码", |
|||
"noAccount": "还没有账号", |
|||
"toRegister": "去注册", |
|||
"and": "和", |
|||
"agreeTips": "登录代表您同意", |
|||
"isAgreeTips": "请先阅读并同意协议", |
|||
"usernameLogin": "密码登录", |
|||
"mobileLogin": "验证码登录", |
|||
"mobilePlaceholder": "请输入手机号" |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
{ |
|||
"registering": "注册中", |
|||
"usernamePlaceholder": "请输入账号", |
|||
"passwordPlaceholder": "请输入密码", |
|||
"confirmPasswordPlaceholder": "请再次确认密码", |
|||
"confirmPasswordError": "两次输入的密码不一致", |
|||
"resetpwd": "忘记密码", |
|||
"haveAccount": "已有账号", |
|||
"toLogin": "去登录", |
|||
"and": "和", |
|||
"registerAgreeTips": "注册代表您同意", |
|||
"isAgreeTips": "请先阅读并同意协议", |
|||
"usernameRegister": "账号注册", |
|||
"mobileRegister": "手机号注册", |
|||
"mobilePlaceholder": "请输入手机号" |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"findPassword": "找回密码", |
|||
"passwordPlaceholder": "请输入密码", |
|||
"confirmPasswordPlaceholder": "请再次确认密码", |
|||
"confirmPasswordError": "两次输入的密码不一致" |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
{ |
|||
"developTitle":"开发环境配置", |
|||
"baseUrl":"API请求地址", |
|||
"imgUrl":"图片服务器地址", |
|||
"siteId":"站点ID(VITE_SITE_ID)", |
|||
"siteIdPlaceholder": "请输入站点ID", |
|||
"pleaseEnterNumber":"请输入数字", |
|||
"maximumCannotExceed":"最大不能超过" |
|||
|
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"alipayAccountNo": "支付宝账号", |
|||
"addBankCard": "添加银行卡", |
|||
"addAlipayAccount": "添加支付宝账号", |
|||
"endNumber": "尾号", |
|||
"bankCard": "银行卡" |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
{ |
|||
"addBankCard": "添加银行卡", |
|||
"addBankCardTips": "请添加持卡人本人的银行卡", |
|||
"addAlipayAccount": "添加支付宝账号", |
|||
"addAlipayAccountTips": "请添加已实名的支付宝账号", |
|||
"bankRealname": "持卡人姓名", |
|||
"bankRealnamePlaceholder": "请输入持卡人姓名", |
|||
"bankName": "银行名称", |
|||
"bankNamePlaceholder": "请输入银行名称", |
|||
"bankAccountNo": "银行卡号", |
|||
"bankAccountNoPlaceholder": "请输入银行卡号", |
|||
"alipayRealname": "真实姓名", |
|||
"alipayRealnamePlaceholder": "请输入真实姓名", |
|||
"alipayAccountNo": "支付宝账号", |
|||
"alipayAccountNoPlaceholder": "请输入支付宝账号", |
|||
"deleteConfirm": "确定要删除该账号吗?" |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"address": "快递地址", |
|||
"locationAddress": "同城配送地址", |
|||
"createAddress": "新建收货地址", |
|||
"default": "默认" |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"name": "收货人", |
|||
"namePlaceholder": "请输入收货人姓名", |
|||
"mobile": "手机号码", |
|||
"mobilePlaceholder": "请输入手机号码", |
|||
"selectArea":"选择地区", |
|||
"selectAreaPlaceholder":"请选择地区", |
|||
"address": "详细地址", |
|||
"addressPlaceholder": "请填写详细地址", |
|||
"defaultAddress": "设为默认地址", |
|||
"selectAddressPlaceholder":"请选择地址" |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
{ |
|||
"cashOutNow": "立即提现", |
|||
"balanceDetail": "余额明细", |
|||
"cashOutTo": "提现到", |
|||
"cashOutTypePlaceholder": "请选择提现方式", |
|||
"wechatpay": "微信默认钱包", |
|||
"cashOutMoneyTip": "提现金额", |
|||
"money": "可提现余额", |
|||
"allTx": "全部提现", |
|||
"minWithdrawal": "最小提现金额为", |
|||
"commissionTo": "手续费为", |
|||
"cashOutList": "提现记录", |
|||
"cashOutToWechat": "提现至微信", |
|||
"cashOutToWechatTips": "提现至微信零钱", |
|||
"cashOutToAlipay": "提现至支付宝", |
|||
"cashOutToAlipayTips": "请先添加支付宝账号", |
|||
"cashOutToBank": "提现至银行卡", |
|||
"cashOutToBankTips": "请先添加银行卡", |
|||
"alipayAccountNo": "支付宝账号", |
|||
"debitCard": "储蓄卡", |
|||
"abnormalOperation": "异常操作", |
|||
"noAvailableCashOutType": "没有可用的提现方式", |
|||
"applyMoneyPlaceholder": "请输入提现金额", |
|||
"moneyformatError": "提现金额格式错误", |
|||
"applyMoneyExceed": "提现金额超出可提现金额", |
|||
"applyMoneyBelow": "提现金额小于最低提现金额", |
|||
"replace": "更换", |
|||
"isOpenApply": "提现设置未开启", |
|||
"toAdd": "添加" |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
{ |
|||
"balanceInfo": "我的余额", |
|||
"recharge": "充值", |
|||
"cashOut":"提现", |
|||
"balanceDetail": "余额明细", |
|||
"accountBalance":"账户余额 (元)", |
|||
"balance":"余额 (元)", |
|||
"money":"可提现余额 (元)", |
|||
"availableBalance": "可用余额", |
|||
"rechargeAmountError": "充值金额错误", |
|||
"clickRecharge": "立即充值", |
|||
"rechargeAmountPlaceholder": "请输入充值金额", |
|||
"yuan":"元", |
|||
"rechargeRecord":"充值记录" |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
{ |
|||
"applyTime": "申请时间", |
|||
"toBeReviewed": "官方正在审核,请耐心等待", |
|||
"toBeTransfer": "官方正在转账,请耐心等待", |
|||
"transfer": "官方已转账,请及时查收", |
|||
"cancelApply": "申请已取消", |
|||
"balanceDetail": "余额记录", |
|||
"commissionDetail": "佣金记录", |
|||
"emptyTip": "暂无提现记录", |
|||
"commissemptyTip": "暂无佣金记录" |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"statusName": "当前状态", |
|||
"cashOutNo": "交易号", |
|||
"serviceMoney": "手续费", |
|||
"createTime": "申请时间", |
|||
"auditTime": "审核时间", |
|||
"transferBank": "银行名称", |
|||
"transferAccount": "收款账号", |
|||
"refuseReason": "拒绝理由", |
|||
"transferTypeName": "转账方式名称", |
|||
"transferTime": "转账时间" |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
{ |
|||
"recharge": "充值", |
|||
"cashOut":"提现", |
|||
"transferMoney":"提现", |
|||
"commissionDetail": "佣金明细", |
|||
"accountCommission":"当前佣金(元)", |
|||
"commission":"累计佣金(元)", |
|||
"money":"提现中佣金(元)", |
|||
"availableCommission": "可用佣金", |
|||
"rechargeAmountError": "充值金额错误", |
|||
"clickRecharge": "立即充值", |
|||
"rechargeAmountPlaceholder": "请输入充值金额", |
|||
"yuan":"元", |
|||
"commissionInfo": "我的佣金" |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
{ |
|||
"balanceDetail": "余额明细", |
|||
"commissionDetail": "佣金明细", |
|||
"balanceEmptyTip": "暂无余额明细", |
|||
"moneyEmptyTip": "暂无提现明细", |
|||
"commissionEmptyTip": "暂无佣金明细" |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
{ |
|||
"name": "收货人", |
|||
"namePlaceholder": "请输入收货人姓名", |
|||
"mobile": "手机号码", |
|||
"mobilePlaceholder": "请输入手机号码", |
|||
"deliveryAddress":"收货地址", |
|||
"selectAddress":"选择地址", |
|||
"selectAddressPlaceholder":"请选择地址", |
|||
"address": "楼号门牌", |
|||
"addressPlaceholder": "详细地址如 1单元101", |
|||
"addressError": "请填写门牌号", |
|||
"defaultAddress": "设为默认地址", |
|||
"update": "修改" |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
{ |
|||
"nickname": "昵称", |
|||
"sex": "性别", |
|||
"mobile": "手机号", |
|||
"birthday": "生日", |
|||
"unknown": "未知", |
|||
"updateHeadimg": "更换头像", |
|||
"updateNickname": "修改昵称", |
|||
"man": "男", |
|||
"woman": "女", |
|||
"bindMobile": "绑定手机" |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"rechargeRecord": "充值记录", |
|||
"emptyTip": "暂无充值记录" |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"orderNo": "订单编号", |
|||
"createTime": "创建时间" |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
{ |
|||
"recharge": "充值", |
|||
"cashOut":"提现", |
|||
"balanceDetail": "余额明细", |
|||
"accountBalance":"账户余额(元)", |
|||
"balance":"余额(元)", |
|||
"money":"可提现余额(元)", |
|||
"availableBalance": "可用余额", |
|||
"rechargeAmountError": "充值金额错误", |
|||
"clickRecharge": "立即充值", |
|||
"rechargeAmountPlaceholder": "请输入充值金额", |
|||
"yuan":"元" |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"personalSettings": "个人设置", |
|||
"switchLang": "切换语言", |
|||
"version": "版本号", |
|||
"logout": "退出登录" |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
<template> |
|||
<view v-if="agreement" class="" style="padding: 30rpx;" :style="themeColor()"> |
|||
<u-parse :content="agreement.content" :tagStyle="{img: 'vertical-align: top;'}"></u-parse> |
|||
</view> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { ref } from 'vue' |
|||
import { onLoad } from '@dcloudio/uni-app' |
|||
import { getAgreementInfo } from '@/app/api/system' |
|||
|
|||
const agreement = ref(null) |
|||
|
|||
onLoad((option)=> { |
|||
getAgreementInfo(option.key).then((res: AnyObject) => { |
|||
agreement.value = res.data |
|||
uni.setNavigationBarTitle({ |
|||
title: res.data.title |
|||
}) |
|||
}) |
|||
}) |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
</style> |
|||
@ -0,0 +1,205 @@ |
|||
<template> |
|||
<view class="w-screen h-screen flex flex-col" :style="themeColor()"> |
|||
<view class="flex-1"> |
|||
<!-- #ifdef H5 --> |
|||
<view class="" style="height: 100rpx;"></view> |
|||
<!-- #endif --> |
|||
<view class="" style="margin-bottom: 100rpx;padding-top: 100rpx;"> |
|||
<view class="font-bold text-lg">{{ t('bindMobile') }}</view> |
|||
</view> |
|||
<view class=""> |
|||
<u-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef"> |
|||
<u-form-item label="" prop="mobile" :border-bottom="true"> |
|||
<u-input v-model="formData.mobile" border="none" clearable :placeholder="t('mobilePlaceholder')" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
<view style="margin-top: 40rpx;"> |
|||
<u-form-item label="" prop="mobile_code" :border-bottom="true"> |
|||
<u-input v-model="formData.mobile_code" border="none" clearable :placeholder="t('codePlaceholder')" class="!bg-transparent" :disabled="real_name_input"> |
|||
<template #suffix> |
|||
<sms-code :mobile="formData.mobile" type="bind_mobile" v-model="formData.mobile_key"></sms-code> |
|||
</template> |
|||
</u-input> |
|||
</u-form-item> |
|||
</view> |
|||
<view class="flex items-start" style="margin-top: 30rpx;" v-if="!info && config.agreement_show"> |
|||
<u-checkbox-group> |
|||
<u-checkbox activeColor="var(--primary-color)" :checked="isAgree" shape="shape" size="14" @change="agreeChange" :customStyle="{'marginTop': '4rpx'}" /> |
|||
</u-checkbox-group> |
|||
<view class="text-xs text-gray-400 flex flex-wrap"> |
|||
{{ t('agreeTips') }} |
|||
<view @click="redirect({ url: '/app/pages/auth/agreement?key=service' })"> |
|||
<text class="text-primary">《{{ t('userAgreement') }}》</text> |
|||
</view> |
|||
<view @click="redirect({ url: '/app/pages/auth/agreement?key=privacy' })"> |
|||
<text class="text-primary">《{{ t('privacyAgreement') }}》</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="" style="margin-top: 60rpx;"> |
|||
<u-button type="primary" :text="t('bind')" :loading="loading" :loadingText="t('binding')" @click="handleBind"></u-button> |
|||
</view> |
|||
<!-- #ifdef MP-WEIXIN --> |
|||
<view class="" style="margin-top: 30rpx;"> |
|||
<u-button type="primary" :plain="true" :text="t('weixinUserAuth')" @click="agreeTips" v-if="!info && config.agreement_show && !isAgree"></u-button> |
|||
<u-button type="primary" :plain="true" :text="t('weixinUserAuth')" open-type="getPhoneNumber" @getphonenumber="mobileAuth" v-else></u-button> |
|||
</view> |
|||
<!-- #endif --> |
|||
</u-form> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- #ifdef MP-WEIXIN --> |
|||
<!-- 小程序隐私协议 --> |
|||
<wx-privacy-popup ref="wxPrivacyPopup"></wx-privacy-popup> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, reactive, computed, onMounted } from 'vue' |
|||
import { t } from '@/locale' |
|||
import { bind } from '@/app/api/auth' |
|||
import { bindMobile } from '@/app/api/member' |
|||
import useMemberStore from '@/stores/member' |
|||
import useConfigStore from '@/stores/config' |
|||
import { useLogin } from '@/hooks/useLogin' |
|||
import { redirect } from '@/utils/common' |
|||
|
|||
const memberStore = useMemberStore() |
|||
const info = computed(() => memberStore.info) |
|||
|
|||
const config = computed(() => { |
|||
return useConfigStore().login |
|||
}) |
|||
|
|||
const loading = ref(false) |
|||
const isAgree = ref(false) |
|||
|
|||
const formData = reactive({ |
|||
mobile: '', |
|||
mobile_code: '', |
|||
mobile_key: '' |
|||
}) |
|||
|
|||
let real_name_input = ref(true); |
|||
onMounted(() => { |
|||
// 防止浏览器自动填充 |
|||
setTimeout(()=>{ |
|||
real_name_input.value = false; |
|||
},800) |
|||
}); |
|||
|
|||
uni.getStorageSync('openid') && (Object.assign(formData, { openid: uni.getStorageSync('openid') })) |
|||
uni.getStorageSync('pid') && (Object.assign(formData, { pid: uni.getStorageSync('pid') })) |
|||
uni.getStorageSync('unionid') && (Object.assign(formData, { unionid: uni.getStorageSync('unionid') })) |
|||
|
|||
const rules = { |
|||
'mobile': [ |
|||
{ |
|||
type: 'string', |
|||
required: true, |
|||
message: t('mobilePlaceholder'), |
|||
trigger: ['blur', 'change'], |
|||
}, |
|||
{ |
|||
validator(rule, value, callback) { |
|||
let mobile = /^1[3-9]\d{9}$/; |
|||
if (!mobile.test(value)){ |
|||
callback(new Error('请输入正确的手机号')) |
|||
} else { |
|||
callback() |
|||
} |
|||
}, |
|||
message: t('mobileError'), |
|||
trigger: ['change', 'blur'], |
|||
} |
|||
], |
|||
'mobile_code': { |
|||
type: 'string', |
|||
required: true, |
|||
message: t('codePlaceholder'), |
|||
trigger: ['blur', 'change'] |
|||
} |
|||
} |
|||
|
|||
const agreeChange = () => { |
|||
isAgree.value = !isAgree.value |
|||
} |
|||
|
|||
const agreeTips = () => { |
|||
uni.showToast({ title: `${t('pleaceAgree')}《${t('userAgreement')}》《${t('privacyAgreement')}》`, icon: 'none' }) |
|||
} |
|||
|
|||
const formRef = ref(null) |
|||
|
|||
const handleBind = () => { |
|||
formRef.value.validate().then(() => { |
|||
if (loading.value) return |
|||
loading.value = true |
|||
|
|||
const request = info.value ? bindMobile : bind |
|||
|
|||
request(formData).then((res) => { |
|||
if (info.value) { |
|||
memberStore.getMemberInfo() |
|||
redirect({ url: '/app/pages/member/personal', mode: 'redirectTo' }) |
|||
} else { |
|||
memberStore.setToken(res.data.token) |
|||
useLogin().handleLoginBack() |
|||
} |
|||
}).catch(() => { |
|||
loading.value = false |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
const mobileAuth = (e) => { |
|||
if (!isAgree.value && !info.value && config.value.agreement_show) { |
|||
uni.showToast({ |
|||
title: `${ t('pleaceAgree') }《${ t('userAgreement') }》《${ t('privacyAgreement') }》`, |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
|
|||
if (e.detail.errMsg == 'getPhoneNumber:ok') { |
|||
uni.showLoading({ title: '' }) |
|||
|
|||
const request = info.value ? bindMobile : bind |
|||
|
|||
request({ |
|||
openid: formData.openid, |
|||
mobile_code: e.detail.code |
|||
}).then((res) => { |
|||
uni.hideLoading() |
|||
if (info.value) { |
|||
memberStore.getMemberInfo() |
|||
redirect({ url: '/app/pages/member/personal', mode: 'redirectTo' }) |
|||
} else { |
|||
memberStore.setToken(res.data.token) |
|||
useLogin().handleLoginBack() |
|||
} |
|||
}).catch((res) => { |
|||
setTimeout(() => { |
|||
uni.hideLoading() |
|||
}, 2000); |
|||
}) |
|||
} |
|||
|
|||
if (e.detail.errno == 104) { |
|||
let msg = '用户未授权隐私权限'; |
|||
uni.showToast({ title: msg, icon: 'none' }) |
|||
} |
|||
if (e.detail.errMsg == "getPhoneNumber:fail user deny") { |
|||
let msg = '用户拒绝获取手机号码'; |
|||
uni.showToast({ title: msg, icon: 'none' }) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.u-input{ |
|||
background-color: transparent !important; |
|||
} |
|||
</style> |
|||
|
|||
@ -0,0 +1,184 @@ |
|||
<template> |
|||
<view class="w-screen h-screen flex flex-col" :style="themeColor()"> |
|||
<view class="flex-1"> |
|||
<!-- #ifdef H5 --> |
|||
<view style="height: 100rpx;"></view> |
|||
<!-- #endif --> |
|||
<view class="" style="padding-top: 100rpx;margin-bottom: 100rpx;"> |
|||
<view class="font-bold text-xl">{{ t('login') }}</view> |
|||
</view> |
|||
<view class=" text-sm flex font-bold leading-none" style="" v-if="loginType.length > 1"> |
|||
<block v-for="(item, index) in loginType"> |
|||
<view :class="{'text-gray-300' : item.type != type}" @click="type = item.type">{{ item.title }}</view> |
|||
<view class=" border-solid border-gray-300" style="border-radius: 2px;max-width: 30rpx;" v-show="index == 0"></view> |
|||
</block> |
|||
</view> |
|||
<view class=""> |
|||
<u-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef"> |
|||
<view v-show="type == 'username'"> |
|||
<u-form-item label="" prop="username" :border-bottom="true"> |
|||
<u-input v-model="formData.username" border="none" clearable :placeholder="t('usernamePlaceholder')" autocomplete="off" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
<view class="" style="margin-top: 40rpx;"> |
|||
<u-form-item label="" prop="password" :border-bottom="true"> |
|||
<u-input v-model="formData.password" border="none" type="password" clearable :placeholder="t('passwordPlaceholder')" autocomplete="new-password" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
</view> |
|||
</view> |
|||
<view v-show="type == 'mobile'"> |
|||
<u-form-item label="" prop="mobile" :border-bottom="true"> |
|||
<u-input v-model="formData.mobile" border="none" clearable :placeholder="t('mobilePlaceholder')" autocomplete="off" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
<view class="" style="margin-top: 40rpx;"> |
|||
<u-form-item label="" prop="mobile_code" :border-bottom="true"> |
|||
<u-input v-model="formData.mobile_code" border="none" clearable class="!bg-transparent" :disabled="real_name_input" :placeholder="t('codePlaceholder')"> |
|||
<template #suffix> |
|||
<sms-code :mobile="formData.mobile" type="login" v-model="formData.mobile_key"></sms-code> |
|||
</template> |
|||
</u-input> |
|||
</u-form-item> |
|||
</view> |
|||
</view> |
|||
<view class="flex text-xs justify-between text-gray-400" style="margin-top: 40rpx;"> |
|||
<view @click="redirect({ url: '/app/pages/auth/register' })">{{ t('noAccount') }} |
|||
<text class="text-primary">{{ t('toRegister') }}</text> |
|||
</view> |
|||
<view @click="redirect({ url: '/app/pages/auth/resetpwd' })">{{ t('resetpwd') }}</view> |
|||
</view> |
|||
<view class="" style="margin-top: 80rpx;"> |
|||
<u-button type="primary" :text="t('login')" :loading="loading" :loadingText="t('logining')" @click="handleLogin"> |
|||
</u-button> |
|||
</view> |
|||
</u-form> |
|||
</view> |
|||
</view> |
|||
<view class="text-xs flex justify-center w-full" style="" v-if="configStore.login.agreement_show"> |
|||
<text class="iconfont " style="font-size: 34rpx;margin-right: 12rpx;" :class="isAgree ? 'iconxuanze1' : 'nc-iconfont nc-icon-yuanquanV6xx'" @click="isAgree = !isAgree"></text> |
|||
{{ t('agreeTips') }} |
|||
<view @click="redirect({ url: '/app/pages/auth/agreement?key=service' })"> |
|||
<text class="text-primary">{{ t('userAgreement') }}</text> |
|||
</view> |
|||
{{ t('and') }} |
|||
<view @click="redirect({ url: '/app/pages/auth/agreement?key=privacy' })"> |
|||
<text class="text-primary">{{ t('privacyAgreement') }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, reactive, computed, onMounted } from 'vue' |
|||
import { usernameLogin, mobileLogin } from '@/app/api/auth' |
|||
import useMemberStore from '@/stores/member' |
|||
import useConfigStore from '@/stores/config' |
|||
import { useLogin } from '@/hooks/useLogin' |
|||
import { t } from '@/locale' |
|||
import { redirect } from '@/utils/common' |
|||
|
|||
const formData = reactive({ |
|||
username: '', |
|||
password: '', |
|||
mobile: '', |
|||
mobile_code: '', |
|||
mobile_key: '' |
|||
}) |
|||
|
|||
let real_name_input = ref(true); |
|||
onMounted(() => { |
|||
// 防止浏览器自动填充 |
|||
setTimeout(()=>{ |
|||
real_name_input.value = false; |
|||
},800) |
|||
}); |
|||
|
|||
if (!uni.getStorageSync('autoLoginLock')) { |
|||
uni.getStorageSync('openid') && (Object.assign(formData, { openid: uni.getStorageSync('openid') })) |
|||
uni.getStorageSync('unionid') && (Object.assign(formData, { unionid: uni.getStorageSync('unionid') })) |
|||
} |
|||
uni.getStorageSync('pid') && (Object.assign(formData, { pid: uni.getStorageSync('pid') })) |
|||
|
|||
const memberStore = useMemberStore() |
|||
const configStore = useConfigStore() |
|||
|
|||
const loading = ref(false) |
|||
|
|||
const type = ref('') |
|||
|
|||
const loginType = computed(() => { |
|||
const value = [] |
|||
configStore.login.is_username && (value.push({ type: 'username', title: t('usernameLogin') })) |
|||
configStore.login.is_mobile && (value.push({ type: 'mobile', title: t('mobileLogin') })) |
|||
type.value = value[0] ? value[0].type : '' |
|||
return value |
|||
}) |
|||
|
|||
const rules = computed(() => { |
|||
return { |
|||
'username': { |
|||
type: 'string', |
|||
required: type.value == 'username', |
|||
message: t('usernamePlaceholder'), |
|||
trigger: ['blur', 'change'], |
|||
}, |
|||
'password': { |
|||
type: 'string', |
|||
required: type.value == 'username', |
|||
message: t('passwordPlaceholder'), |
|||
trigger: ['blur', 'change'] |
|||
}, |
|||
'mobile': [ |
|||
{ |
|||
type: 'string', |
|||
required: type.value == 'mobile', |
|||
message: t('mobilePlaceholder'), |
|||
trigger: ['blur', 'change'], |
|||
}, |
|||
{ |
|||
validator(rule, value) { |
|||
if (type.value != 'mobile') return true |
|||
else return uni.$u.test.mobile(value) |
|||
}, |
|||
message: t('mobileError'), |
|||
trigger: ['change', 'blur'], |
|||
} |
|||
], |
|||
'mobile_code': { |
|||
type: 'string', |
|||
required: type.value == 'mobile', |
|||
message: t('codePlaceholder'), |
|||
trigger: ['blur', 'change'] |
|||
} |
|||
} |
|||
}) |
|||
|
|||
const isAgree = ref(false) |
|||
|
|||
const formRef = ref(null) |
|||
|
|||
const handleLogin = () => { |
|||
formRef.value.validate().then(() => { |
|||
if (configStore.login.agreement_show && !isAgree.value) { |
|||
uni.showToast({ title: t('isAgreeTips'), icon: 'none' }); |
|||
return false; |
|||
} |
|||
|
|||
if (loading.value) return |
|||
loading.value = true |
|||
|
|||
const login = type.value == 'username' ? usernameLogin : mobileLogin |
|||
|
|||
login(formData).then((res) => { |
|||
memberStore.setToken(res.data.token) |
|||
useLogin().handleLoginBack() |
|||
}).catch(() => { |
|||
loading.value = false |
|||
}) |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.u-input{ |
|||
background-color: transparent !important; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,229 @@ |
|||
<template> |
|||
<view class="w-screen h-screen flex flex-col" :style="themeColor()"> |
|||
<view class="flex-1"> |
|||
<!-- #ifdef H5 --> |
|||
<view class="" style="height: 100rpx;"></view> |
|||
<!-- #endif --> |
|||
<view class="" style="padding-top: 100rpx;margin-bottom: 100rpx;"> |
|||
<view class="font-bold text-xl">{{ t('register') }}</view> |
|||
</view> |
|||
<view class=" text-sm flex font-bold leading-none" style="margin-bottom: 50rpx;" v-if="registerType.length > 1"> |
|||
<block v-for="(item, index) in registerType"> |
|||
<view :class="{'text-gray-300' : item.type != type}" @click="type = item.type">{{ item.title }}</view> |
|||
<view class=" border-solid border-0 border-gray-300" style="border-radius: 2px;" v-show="index == 0"></view> |
|||
</block> |
|||
</view> |
|||
<view class=""> |
|||
<u-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef"> |
|||
<view v-show="type == 'username'"> |
|||
<view class="" style="margin-top: 30rpx;"> |
|||
<u-form-item label="" prop="username" :border-bottom="true"> |
|||
<u-input v-model="formData.username" border="none" clearable :placeholder="t('usernamePlaceholder')" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
</view> |
|||
<view style="margin-top: 30rpx;"> |
|||
<u-form-item label="" prop="password" :border-bottom="true"> |
|||
<u-input v-model="formData.password" border="none" type="password" clearable :placeholder="t('passwordPlaceholder')" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
</view> |
|||
<view style="margin-top: 30rpx;"> |
|||
<u-form-item label="" prop="confirm_password" :border-bottom="true"> |
|||
<u-input v-model="formData.confirm_password" border="none" type="password" clearable :placeholder="t('confirmPasswordPlaceholder')" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
</view> |
|||
</view> |
|||
<view v-show="type == 'mobile' || configStore.login.is_bind_mobile"> |
|||
<view style="margin-top: 30rpx;"> |
|||
<u-form-item label="" prop="mobile" :border-bottom="true"> |
|||
<u-input v-model="formData.mobile" border="none" clearable :placeholder="t('mobilePlaceholder')" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
</view> |
|||
<view style="margin-top: 30rpx;"> |
|||
<u-form-item label="" prop="code" :border-bottom="true"> |
|||
<u-input v-model="formData.mobile_code" border="none" clearable :placeholder="t('codePlaceholder')" class="!bg-transparent" :disabled="real_name_input"> |
|||
<template #suffix> |
|||
<sms-code :mobile="formData.mobile" type="register" v-model="formData.mobile_key"></sms-code> |
|||
</template> |
|||
</u-input> |
|||
</u-form-item> |
|||
</view> |
|||
</view> |
|||
<view v-show="type == 'username'"> |
|||
<view style="margin-top: 30rpx;"> |
|||
<u-form-item label="" prop="captcha_code" :border-bottom="true"> |
|||
<u-input v-model="formData.captcha_code" border="none" clearable :placeholder="t('captchaPlaceholder')" class="!bg-transparent" :disabled="real_name_input"> |
|||
<template #suffix> |
|||
<image :src="captcha.image.value" class="" style="height: 48rpx;margin-left: 20rpx;" mode="heightFix" @click="captcha.refresh()"></image> |
|||
</template> |
|||
</u-input> |
|||
</u-form-item> |
|||
</view> |
|||
</view> |
|||
<view class="flex text-xs justify-between text-gray-400" style="margin-top: 20rpx;"> |
|||
<view @click="redirect({ url: '/app/pages/auth/login' })">{{ t('haveAccount') }},<text class="text-primary">{{ t('toLogin') }}</text></view> |
|||
</view> |
|||
<view class="" style="margin-top: 80rpx;"> |
|||
<u-button type="primary" :text="t('register')" :loading="loading" :loadingText="t('registering')" @click="handleRegister"> |
|||
</u-button> |
|||
</view> |
|||
</u-form> |
|||
</view> |
|||
</view> |
|||
<view class="text-xs flex justify-center w-full" style="" v-if="configStore.login.agreement_show"> |
|||
<text class="iconfont" style="font-style: 34rpx;margin-right: 12rpx;" :class="isAgree ? 'iconxuanze1' : 'nc-iconfont nc-icon-yuanquanV6xx'" @click="isAgree = !isAgree"></text> |
|||
{{ t('registerAgreeTips') }} |
|||
<view @click="redirect({ url: '/app/pages/auth/agreement?key=service' })"> |
|||
<text class="text-primary">{{ t('userAgreement') }}</text> |
|||
</view> |
|||
{{ t('and') }} |
|||
<view @click="redirect({ url: '/app/pages/auth/agreement?key=privacy' })"> |
|||
<text class="text-primary">{{ t('privacyAgreement') }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, reactive, computed, onMounted } from 'vue' |
|||
import { usernameRegister, mobileRegister } from '@/app/api/auth' |
|||
import useMemberStore from '@/stores/member' |
|||
import useConfigStore from '@/stores/config' |
|||
import { useLogin } from '@/hooks/useLogin' |
|||
import { useCaptcha } from '@/hooks/useCaptcha' |
|||
import { t } from '@/locale' |
|||
import { redirect } from '@/utils/common' |
|||
|
|||
const formData = reactive({ |
|||
username: '', |
|||
password: '', |
|||
confirm_password: '', |
|||
mobile: '', |
|||
mobile_code: '', |
|||
mobile_key: '', |
|||
captcha_key: '', |
|||
captcha_code: '' |
|||
}) |
|||
|
|||
let real_name_input = ref(true); |
|||
onMounted(() => { |
|||
// 防止浏览器自动填充 |
|||
setTimeout(()=>{ |
|||
real_name_input.value = false; |
|||
},800) |
|||
}); |
|||
|
|||
if (!uni.getStorageSync('autoLoginLock')) { |
|||
uni.getStorageSync('openid') && (Object.assign(formData, {openid: uni.getStorageSync('openid')})) |
|||
uni.getStorageSync('pid') && (Object.assign(formData, {pid: uni.getStorageSync('pid')})) |
|||
} |
|||
uni.getStorageSync('unionid') && (Object.assign(formData, { unionid: uni.getStorageSync('unionid') })) |
|||
|
|||
const captcha = useCaptcha(formData) |
|||
captcha.refresh() |
|||
|
|||
const memberStore = useMemberStore() |
|||
const configStore = useConfigStore() |
|||
|
|||
const loading = ref(false) |
|||
|
|||
const type = ref('') |
|||
|
|||
const registerType = computed(()=> { |
|||
const value = [] |
|||
configStore.login.is_username && (value.push({ type: 'username', title: t('usernameRegister') })) |
|||
configStore.login.is_mobile && !configStore.login.is_bind_mobile && (value.push({ type: 'mobile', title: t('mobileRegister') })) |
|||
type.value = value[0] ? value[0].type : '' |
|||
return value |
|||
}) |
|||
|
|||
const rules = computed(()=>{ |
|||
return { |
|||
'username': { |
|||
type: 'string', |
|||
required: type.value == 'username', |
|||
message: t('usernamePlaceholder'), |
|||
trigger: ['blur', 'change'], |
|||
}, |
|||
'password': { |
|||
type: 'string', |
|||
required: type.value == 'username', |
|||
message: t('passwordPlaceholder'), |
|||
trigger: ['blur', 'change'] |
|||
}, |
|||
'confirm_password': [ |
|||
{ |
|||
type: 'string', |
|||
required: type.value == 'username', |
|||
message: t('confirmPasswordPlaceholder'), |
|||
trigger: ['blur', 'change'] |
|||
}, |
|||
{ |
|||
validator(rule, value){ |
|||
return value == formData.password |
|||
}, |
|||
message: t('confirmPasswordError'), |
|||
trigger: ['change','blur'], |
|||
} |
|||
], |
|||
'mobile': [ |
|||
{ |
|||
type: 'string', |
|||
required: type.value == 'mobile' || configStore.login.is_bind_mobile, |
|||
message: t('mobilePlaceholder'), |
|||
trigger: ['blur', 'change'], |
|||
}, |
|||
{ |
|||
validator(rule, value){ |
|||
if (type.value != 'mobile' && !configStore.login.is_bind_mobile) return true |
|||
else return uni.$u.test.mobile(value) |
|||
}, |
|||
message: t('mobileError'), |
|||
trigger: ['change','blur'], |
|||
} |
|||
], |
|||
'mobile_code': { |
|||
type: 'string', |
|||
required: type.value == 'mobile' || configStore.login.is_bind_mobile, |
|||
message: t('codePlaceholder'), |
|||
trigger: ['blur', 'change'] |
|||
}, |
|||
'captcha_code': { |
|||
type: 'string', |
|||
required: type.value == 'username', |
|||
message: t('captchaPlaceholder'), |
|||
trigger: ['blur', 'change'], |
|||
} |
|||
} |
|||
}) |
|||
|
|||
const isAgree = ref(false) |
|||
|
|||
const formRef = ref(null) |
|||
|
|||
const handleRegister = () => { |
|||
formRef.value.validate().then(() => { |
|||
if (configStore.login.agreement_show && !isAgree.value) { |
|||
uni.showToast({ title: t('isAgreeTips'), icon: 'none' }); |
|||
return false; |
|||
} |
|||
if (loading.value) return |
|||
loading.value = true |
|||
|
|||
const register = type.value == 'username' ? usernameRegister : mobileRegister |
|||
|
|||
register(formData).then((res: responseResult) => { |
|||
memberStore.setToken(res.data.token) |
|||
useLogin().handleLoginBack() |
|||
}).catch(() => { |
|||
loading.value = false |
|||
captcha.refresh() |
|||
}) |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.u-input{ |
|||
background-color: transparent !important; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,135 @@ |
|||
<template> |
|||
<view class="w-screen h-screen flex flex-col" :style="themeColor()"> |
|||
<view class="flex-1"> |
|||
<!-- #ifdef H5 --> |
|||
<view class="" style="100rpx"></view> |
|||
<!-- #endif --> |
|||
<view class="" style="padding-top: 100rpx;margin-bottom: 100rpx;"> |
|||
<view class="font-bold text-xl">{{ t('findPassword') }}</view> |
|||
</view> |
|||
|
|||
<view class=""> |
|||
<u-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef"> |
|||
<view style="margin-top: 30rpx;"> |
|||
<u-form-item label="" prop="mobile" :border-bottom="true"> |
|||
<u-input v-model="formData.mobile" border="none" clearable :placeholder="t('mobilePlaceholder')" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
</view> |
|||
<view style="margin-top: 30rpx;"> |
|||
<u-form-item label="" prop="code" :border-bottom="true"> |
|||
<u-input v-model="formData.mobile_code" border="none" clearable :placeholder="t('codePlaceholder')" class="!bg-transparent" :disabled="real_name_input"> |
|||
<template #suffix> |
|||
<sms-code :mobile="formData.mobile" type="find_pass" v-model="formData.mobile_key"></sms-code> |
|||
</template> |
|||
</u-input> |
|||
</u-form-item> |
|||
</view> |
|||
<view style="margin-top: 30rpx;"> |
|||
<u-form-item label="" prop="password" :border-bottom="true"> |
|||
<u-input v-model="formData.password" border="none" type="password" clearable :placeholder="t('passwordPlaceholder')" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
</view> |
|||
<view style="margin-top: 30rpx;"> |
|||
<u-form-item label="" prop="confirm_password" :border-bottom="true"> |
|||
<u-input v-model="formData.confirm_password" border="none" type="password" clearable :placeholder="t('confirmPasswordPlaceholder')" class="!bg-transparent" :disabled="real_name_input"/> |
|||
</u-form-item> |
|||
</view> |
|||
<view class="" style="margin-top: 80rpx;"> |
|||
<u-button type="primary" :text="t('confirm')" :loading="loading" :loadingText="t('confirm')" @click="handleConfirm"> |
|||
</u-button> |
|||
</view> |
|||
</u-form> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, reactive, onMounted } from 'vue' |
|||
import { t } from '@/locale' |
|||
import { resetPassword } from '@/app/api/system' |
|||
import { redirect } from '@/utils/common' |
|||
|
|||
const formData = reactive({ |
|||
mobile: '', |
|||
mobile_code: '', |
|||
mobile_key: '', |
|||
password: '', |
|||
confirm_password: '' |
|||
}) |
|||
|
|||
let real_name_input = ref(true); |
|||
onMounted(() => { |
|||
// 防止浏览器自动填充 |
|||
setTimeout(()=>{ |
|||
real_name_input.value = false; |
|||
},800) |
|||
}); |
|||
|
|||
const loading = ref(false) |
|||
const formRef = ref(null) |
|||
|
|||
const rules = { |
|||
'password': { |
|||
type: 'string', |
|||
required: true, |
|||
message: t('passwordPlaceholder'), |
|||
trigger: ['blur', 'change'] |
|||
}, |
|||
'confirm_password': [ |
|||
{ |
|||
type: 'string', |
|||
required: true, |
|||
message: t('confirmPasswordPlaceholder'), |
|||
trigger: ['blur', 'change'] |
|||
}, |
|||
{ |
|||
validator(rule, value) { |
|||
return value == formData.password |
|||
}, |
|||
message: t('confirmPasswordError'), |
|||
trigger: ['change', 'blur'], |
|||
} |
|||
], |
|||
'mobile': [ |
|||
{ |
|||
type: 'string', |
|||
required: true, |
|||
message: t('mobilePlaceholder'), |
|||
trigger: ['blur', 'change'], |
|||
}, |
|||
{ |
|||
validator(rule, value) { |
|||
return uni.$u.test.mobile(value) |
|||
}, |
|||
message: t('mobileError'), |
|||
trigger: ['change', 'blur'], |
|||
} |
|||
], |
|||
'mobile_code': { |
|||
type: 'string', |
|||
required: true, |
|||
message: t('codePlaceholder'), |
|||
trigger: ['blur', 'change'] |
|||
} |
|||
} |
|||
|
|||
const handleConfirm = () => { |
|||
formRef.value.validate().then(() => { |
|||
if (loading.value) return |
|||
loading.value = true |
|||
|
|||
resetPassword(formData).then((res : responseResult) => { |
|||
redirect({ url: '/app/pages/auth/login', mode: 'redirectTo' }) |
|||
}).catch(() => { |
|||
loading.value = false |
|||
}) |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.u-input{ |
|||
background-color: transparent !important; |
|||
} |
|||
</style> |
|||
|
After Width: | Height: | Size: 355 B |
@ -0,0 +1,156 @@ |
|||
<script setup> |
|||
import { onLoad } from '@dcloudio/uni-app'; |
|||
import { computed, ref } from 'vue'; |
|||
import { getProductDetails, orderCalculation, orderCreation } from '@/app/api/home'; |
|||
|
|||
const imgUrl = import.meta.env.VITE_IMG_DOMAIN; |
|||
|
|||
const details = ref({}); |
|||
const id = ref() |
|||
|
|||
const getDetails = async () => { |
|||
const res = await getProductDetails({ goods_id: id.value }); |
|||
console.log(res); |
|||
details.value = res.data; |
|||
uni.setNavigationBarTitle({ |
|||
title: details.value.goods.goods_name |
|||
}); |
|||
}; |
|||
|
|||
const buy = async () => { |
|||
// 第一步:计算 |
|||
const res = await orderCalculation({ |
|||
sku_data: [ { |
|||
sku_id: details.value.sku_id, |
|||
num: 1 |
|||
} ] |
|||
}); |
|||
if (!res) { |
|||
return uni.showToast({ |
|||
title: '购买失败' |
|||
}); |
|||
} |
|||
console.log(res); |
|||
|
|||
|
|||
const res2 = await orderCreation({ |
|||
order_key: res.data.order_key, |
|||
member_remark: '' |
|||
}); |
|||
|
|||
if (!res2) { |
|||
return uni.showToast({ |
|||
title: '购买失败' |
|||
}); |
|||
} |
|||
uni.navigateTo({ |
|||
url: '../../../addon/mine/orderList/orderList?barName=服务订单&is_shop=1', |
|||
}); |
|||
}; |
|||
|
|||
onLoad(async (o) => { |
|||
id.value = o.id |
|||
uni.showLoading() |
|||
await getDetails();+ |
|||
uni.hideLoading() |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="commodity"> |
|||
<img :src="imgUrl + 'addon/system/backage_3.png'" class="commodity-cover" alt=""/> |
|||
<image mode="aspectFill" :src="details.goods.goods_cover" class="commodity-cover" alt=""/> |
|||
<div class="commodity-card"> |
|||
<p class="commodity-card-title">{{ details.goods.goods_name }}</p> |
|||
<p class="commodity-card-description" v-html="details.goods.goods_desc"></p> |
|||
</div> |
|||
<div class="commodity-footer"> |
|||
<div class="commodity-footer-price"> |
|||
<div class="commodity-footer-price-iconContainer"> |
|||
<img class="commodity-footer-price-iconContainer-img" src="./commodity.png" alt=""/> |
|||
</div> |
|||
¥{{ details.price }} |
|||
</div> |
|||
<up-button @click="buy" style="display: inline-flex" shape="circle" color="#4B81FB" |
|||
text="立即购买"></up-button> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped lang="scss"> |
|||
|
|||
.commodity { |
|||
width: 100%; |
|||
height: 100vh; |
|||
position: relative; |
|||
background: #F5F8F7; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: space-between; |
|||
|
|||
&-cover { |
|||
width: 100%; |
|||
height: 30vh; |
|||
object-fit: contain; |
|||
} |
|||
|
|||
&-card { |
|||
width: 90%; |
|||
height: 60vh; |
|||
background: white; |
|||
border-radius: 10px; |
|||
margin: -10vh auto 0 auto; |
|||
position: relative; |
|||
z-index: 1; |
|||
padding: 20px; |
|||
box-sizing: border-box; |
|||
overflow: auto; |
|||
|
|||
&-title { |
|||
font-size: 20px; |
|||
margin-bottom: 10px; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
&-description { |
|||
font-size: 16px; |
|||
line-height: 30px; |
|||
text-align: justify; |
|||
} |
|||
} |
|||
|
|||
&-footer { |
|||
background: white; |
|||
height: 10vh; |
|||
display: flex; |
|||
padding: 20px 30px; |
|||
box-sizing: border-box; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
gap: 20px; |
|||
|
|||
&-price { |
|||
display: flex; |
|||
align-items: center; |
|||
font-size: 20px; |
|||
gap: 10px; |
|||
|
|||
&-iconContainer { |
|||
width: 35px; |
|||
height: 35px; |
|||
border: 1px solid #86A8FF; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
background: rgba(75, 129, 251, 0.1); |
|||
border-radius: 100%; |
|||
|
|||
&-img { |
|||
width: 20px; |
|||
height: 20px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,13 @@ |
|||
//选举列表 |
|||
<template> |
|||
|
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { ref } from 'vue' |
|||
|
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
|
|||
</style> |
|||
@ -0,0 +1,14 @@ |
|||
//选举结果 |
|||
<template> |
|||
|
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { onLoad, onShow } from '@dcloudio/uni-app'; |
|||
import { reactive, ref } from 'vue'; |
|||
|
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
|
|||
</style> |
|||
@ -0,0 +1,14 @@ |
|||
//个人信息 |
|||
<template> |
|||
|
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { reactive, ref } from 'vue'; |
|||
|
|||
</script> |
|||
|
|||
|
|||
<style> |
|||
|
|||
</style> |
|||
@ -0,0 +1,14 @@ |
|||
//我的选举 |
|||
<template> |
|||
|
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { reactive, ref } from 'vue'; |
|||
|
|||
|
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
|
|||
</style> |
|||
@ -0,0 +1,120 @@ |
|||
//投票选举 |
|||
<template> |
|||
<view class="box"> |
|||
<view class="main"> |
|||
<view class="headpart"> |
|||
<view class="title"> |
|||
2024年度董事会成员选举 |
|||
</view> |
|||
<view class="time"> |
|||
投票开始时间:2024年3月15日 18:00 |
|||
</view> |
|||
<view class="time"> |
|||
投票截止时间:2024年3月15日 18:00 |
|||
</view> |
|||
</view> |
|||
<view class="tppart"> |
|||
<view class="tpone" v-for="(item,index) in tpList" :key="index"> |
|||
<img style="width: 96rpx;height: 96rpx; border-radius: 50%;" :src="item.img" alt="" /> |
|||
<view class="rightpart"> |
|||
<view class="name"> |
|||
{{item.name}} |
|||
</view> |
|||
<view class="class"> |
|||
|
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view class="buts"> |
|||
|
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { |
|||
onMounted, |
|||
ref |
|||
} from 'vue'; |
|||
|
|||
const tpList = ref([ |
|||
{ |
|||
img: '/static/img/grxx.png', |
|||
name: '陈志远', |
|||
class: '现任财务总监', |
|||
} |
|||
]) |
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
.box { |
|||
width: 100%; |
|||
height: 100vh; |
|||
background-color: #F9FAFB; |
|||
|
|||
.main { |
|||
width: 100%; |
|||
height: 90vh; |
|||
display: grid; |
|||
justify-items: center; |
|||
align-content: start; |
|||
|
|||
.headpart { |
|||
width: 86%; |
|||
height: 194rpx; |
|||
border-radius: 24rpx; |
|||
background-color: #EFF6FF; |
|||
margin-top: 32rpx; |
|||
padding: 20rpx 3%; |
|||
|
|||
.title { |
|||
color: #2563EB; |
|||
font-size: 28rpx; |
|||
margin-top: 16rpx; |
|||
} |
|||
|
|||
.time { |
|||
margin-top: 16rpx; |
|||
color: #4B5563; |
|||
font-size: 28rpx; |
|||
} |
|||
} |
|||
|
|||
.tppart { |
|||
margin-top: 32rpx; |
|||
width: 100%; |
|||
max-height: 60vh; |
|||
overflow-y: auto; |
|||
display: grid; |
|||
justify-items: center; |
|||
|
|||
.tpone { |
|||
width: 84%; |
|||
height: 264rpx; |
|||
padding: 20rpx 4%; |
|||
border-radius: 24rpx; |
|||
background-color: #FFFFFF; |
|||
border: 2rpx solid #F3F4F6; |
|||
box-shadow: 5rpx 5rpx 3rpx 0 rgba(212, 212, 212, 0.7); |
|||
.rightpart { |
|||
margin-left: 40rpx; |
|||
.name { |
|||
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.buts { |
|||
width: 100%; |
|||
height: 10vh; |
|||
background-color: F3F4F6; |
|||
position: fixed; |
|||
bottom: 0; |
|||
border-top: 2rpx solid #ebebec; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,33 @@ |
|||
<template> |
|||
<view :style="themeColor()"> |
|||
<u-icon name="arrow-left" class="navigate-back" @click="navigateBack"></u-icon> |
|||
<web-view :src="src"></web-view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { onLoad } from '@dcloudio/uni-app'; |
|||
import { ref } from 'vue'; |
|||
|
|||
const src = ref('') |
|||
|
|||
onLoad((option : any) => { |
|||
src.value = decodeURIComponent(option.src); |
|||
}) |
|||
|
|||
const navigateBack = () => { |
|||
uni.navigateBack({ |
|||
delta: 1 |
|||
}); |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.navigate-back { |
|||
position: absolute; |
|||
top: 34rpx; |
|||
left: 34rpx; |
|||
z-index: 999; |
|||
font-size: 16px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,138 @@ |
|||
import { defineStore } from 'pinia' |
|||
import { toRaw } from 'vue' |
|||
import { diyRedirect, currRoute, getToken } from '@/utils/common'; |
|||
import { useLogin } from '@/hooks/useLogin'; |
|||
|
|||
interface Diy { |
|||
mode: string, // 模式:decorate 装修,为空表示正常
|
|||
pageMode: string, // 页面展示模式,diy:自定义,fixed:固定
|
|||
currentIndex: number, |
|||
global: { |
|||
title: string, |
|||
pageStartBgColor: string, // 页面背景颜色(开始)
|
|||
pageEndBgColor: string, // 页面背景颜色(结束)
|
|||
bottomTabBarSwitch: boolean, // 底部导航开关
|
|||
bgUrl: string |
|||
}, |
|||
// 组件集合
|
|||
value: any[], |
|||
topFixedStatus: string, // 置顶组件的状态
|
|||
scrollTop: number, |
|||
topTabarHeight: number |
|||
} |
|||
|
|||
const useDiyStore = defineStore('diy', { |
|||
state: (): Diy => { |
|||
return { |
|||
mode: '', |
|||
pageMode: 'diy', |
|||
currentIndex: -99, |
|||
global: { |
|||
title: "", |
|||
pageStartBgColor: '', // 页面背景颜色(开始)
|
|||
pageEndBgColor: '', // 页面背景颜色(结束)
|
|||
bottomTabBarSwitch: true, |
|||
bgUrl: '' |
|||
}, |
|||
value: [], // 组件集合
|
|||
topFixedStatus: 'home', // 顶部 置顶组件状态,home:展示首页数据、diy:展示置顶组件定义的子页面
|
|||
scrollTop: 0, // 滚动位置
|
|||
topTabarHeight: 0 |
|||
} |
|||
}, |
|||
getters: {}, |
|||
actions: { |
|||
// 初始化
|
|||
init() { |
|||
// #ifdef H5
|
|||
var data = JSON.stringify({ |
|||
type: 'init', |
|||
load: true |
|||
}); |
|||
// 传输给后台数据
|
|||
window.parent.postMessage(data, '*'); |
|||
|
|||
// 监听父页面发来的消息
|
|||
window.addEventListener('message', event => { |
|||
try { |
|||
let data = JSON.parse(event.data); |
|||
this.currentIndex = data.currentIndex; |
|||
this.pageMode = data.pageMode; |
|||
if (data.global) this.global = data.global; |
|||
if (data.value) this.value = data.value; |
|||
|
|||
if (this.value) { |
|||
this.value.forEach((item, index) => { |
|||
item.pageStyle = ''; |
|||
if (item.pageStartBgColor) { |
|||
if (item.pageStartBgColor && item.pageEndBgColor) item.pageStyle += `background:linear-gradient(${ item.pageGradientAngle },${ item.pageStartBgColor },${ item.pageEndBgColor });`; |
|||
else item.pageStyle += 'background-color:' + item.pageStartBgColor + ';'; |
|||
} |
|||
|
|||
if (item.margin) { |
|||
if (item.margin.top > 0) { |
|||
item.pageStyle += 'padding-top:' + item.margin.top * 2 + 'rpx' + ';'; |
|||
} else { |
|||
item.pageStyle += 'padding-top:2rpx' + ';'; // 装修实时预览需要设置
|
|||
} |
|||
item.pageStyle += 'padding-bottom:' + item.margin.bottom * 2 + 'rpx' + ';'; |
|||
item.pageStyle += 'padding-right:' + item.margin.both * 2 + 'rpx' + ';'; |
|||
item.pageStyle += 'padding-left:' + item.margin.both * 2 + 'rpx' + ';'; |
|||
} |
|||
}); |
|||
} |
|||
// console.log('uniapp 接受后台装修返回的组件数据', data);
|
|||
} catch (e) { |
|||
console.log('uni-app diy 接受数据错误', e) |
|||
} |
|||
}, false); |
|||
// #endif
|
|||
}, |
|||
// 将数据传输给后台
|
|||
postMessage(index: any, component: any) { |
|||
// #ifdef H5
|
|||
this.currentIndex = index; |
|||
if (component) |
|||
var data: any = JSON.stringify({ |
|||
type: 'data', |
|||
index: this.currentIndex, |
|||
global: toRaw(this.global), |
|||
value: toRaw(this.value), |
|||
component: toRaw(component) |
|||
}); |
|||
// 传输给后台数据
|
|||
window.parent.postMessage(data, '*'); |
|||
// #endif
|
|||
}, |
|||
// 选中正在编辑的组件
|
|||
changeCurrentIndex(index: number, component: any = null) { |
|||
// #ifdef H5
|
|||
|
|||
// 实际展示禁止编辑
|
|||
if (this.mode == '') return; |
|||
|
|||
// 减少重复请求
|
|||
if (this.currentIndex == index) return; |
|||
this.currentIndex = index; |
|||
var data = JSON.stringify({ |
|||
type: 'change', |
|||
index, |
|||
component: toRaw(component) |
|||
}); |
|||
window.parent.postMessage(data, '*'); |
|||
// #endif
|
|||
}, |
|||
toRedirect(data: any) { |
|||
if (Object.keys(data).length) { |
|||
if (!data.name) return; |
|||
if (currRoute() == 'app/pages/member/index' && !getToken()) { |
|||
useLogin().setLoginBack({ url: data.url }) |
|||
return; |
|||
} |
|||
diyRedirect(data); |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
|
|||
export default useDiyStore |
|||
@ -0,0 +1,154 @@ |
|||
<template> |
|||
<u-popup :show="show" @close="show = false" mode="bottom" :round="10" :closeable="true"> |
|||
<view class="text-center p-[30rpx]">请选择地区</view> |
|||
|
|||
<view class="flex p-[30rpx] text-sm font-semibold"> |
|||
<view v-if="areaList.province.length" class="flex-1" :class="{'text-[var(--primary-color)]': currSelect == 'province'}" @click="currSelect = 'province'"> |
|||
<view v-if="selected.province">{{ selected.province.name }}</view> |
|||
<view v-else>请选择</view> |
|||
</view> |
|||
<view v-if="areaList.city.length" class="flex-1" :class="{'text-[var(--primary-color)]': currSelect == 'city' }" @click="currSelect = 'city'"> |
|||
<view v-if="selected.city">{{ selected.city.name }}</view> |
|||
<view v-else>请选择</view> |
|||
</view> |
|||
<view v-if="areaList.district.length" class="flex-1" :class="{'text-[var(--primary-color)]': currSelect == 'district' }" @click="currSelect = 'district'"> |
|||
<view v-if="selected.district">{{ selected.district.name }}</view> |
|||
<view v-else>请选择</view> |
|||
</view> |
|||
</view> |
|||
<scroll-view scroll-y="true" class="h-[50vh]"> |
|||
<view class="flex p-[30rpx] pt-0 text-sm"> |
|||
<view v-if="areaList.province.length" v-show="currSelect == 'province'"> |
|||
<view v-for="item in areaList.province" class="leading-loose" :class="{'text-[var(--primary-color)]': selected.province && selected.province.id == item.id }" |
|||
@click="selected.province = item" >{{ item.name }}</view> |
|||
</view> |
|||
<view v-if="areaList.city.length" v-show="currSelect == 'city'"> |
|||
<view v-for="item in areaList.city" class="leading-loose" :class="{'text-[var(--primary-color)]': selected.city && selected.city.id == item.id }" |
|||
@click="selected.city = item">{{ item.name }}</view> |
|||
</view> |
|||
<view v-if="areaList.district.length" v-show="currSelect == 'district'"> |
|||
<view v-for="item in areaList.district" class="leading-loose" :class="{'text-[var(--primary-color)]': selected.district && selected.district.id == item.id }" |
|||
@click="selected.district = item">{{ item.name }}</view> |
|||
</view> |
|||
</view> |
|||
</scroll-view> |
|||
</u-popup> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, reactive, watch } from 'vue' |
|||
import { getAreaListByPid, getAreaByCode } from '@/app/api/system' |
|||
|
|||
const prop = defineProps({ |
|||
areaId: { |
|||
type: Number, |
|||
default: 0 |
|||
} |
|||
}) |
|||
|
|||
const show = ref(false) |
|||
const areaList = reactive({ |
|||
province: [], |
|||
city: [], |
|||
district: [] |
|||
}) |
|||
const currSelect = ref('province') |
|||
|
|||
const selected = reactive({ |
|||
province: null, |
|||
city: null, |
|||
district: null |
|||
}) |
|||
|
|||
getAreaListByPid(0).then(({ data }) => { |
|||
areaList.province = data |
|||
}).catch() |
|||
|
|||
watch(() => prop.areaId, (nval, oval)=> { |
|||
if (nval && !oval) { |
|||
getAreaByCode(nval).then(({ data }) => { |
|||
data.province && (selected.province = data.province) |
|||
data.city && (selected.city = data.city) |
|||
data.district && (selected.district = data.district) |
|||
}) |
|||
.catch() |
|||
} |
|||
},{ |
|||
immediate:true |
|||
}) |
|||
|
|||
/** |
|||
* 监听省变更 |
|||
*/ |
|||
watch(() => selected.province, ()=> { |
|||
getAreaListByPid(selected.province.id).then(({ data }) => { |
|||
areaList.city = data |
|||
currSelect.value = 'city' |
|||
|
|||
if (selected.city) { |
|||
let isExist = false |
|||
for (let i = 0; i < data.length; i++) { |
|||
if (selected.city.id == data[i].id) { |
|||
isExist = true |
|||
break |
|||
} |
|||
} |
|||
if (!isExist) { |
|||
selected.city = null |
|||
} |
|||
} |
|||
}).catch() |
|||
}, { deep: true }) |
|||
|
|||
/** |
|||
* 监听市变更 |
|||
*/ |
|||
watch(() => selected.city, (nval)=> { |
|||
if (nval) { |
|||
getAreaListByPid(selected.city.id).then(({ data }) => { |
|||
areaList.district = data |
|||
currSelect.value = 'district' |
|||
|
|||
if (selected.district) { |
|||
let isExist = false |
|||
for (let i = 0; i < data.length; i++) { |
|||
if (selected.district.id == data[i].id) { |
|||
isExist = true |
|||
break |
|||
} |
|||
} |
|||
if (!isExist) { |
|||
selected.district = null |
|||
} |
|||
} |
|||
}).catch() |
|||
} else { |
|||
areaList.district = [] |
|||
selected.district = null |
|||
} |
|||
|
|||
}, { deep: true }) |
|||
|
|||
const emits = defineEmits(['complete']) |
|||
|
|||
/** |
|||
* 监听区县变更 |
|||
*/ |
|||
watch(() => selected.district, (nval)=> { |
|||
if (nval) { |
|||
currSelect.value = 'district' |
|||
emits('complete', selected) |
|||
show.value = false |
|||
} |
|||
}, { deep: true }) |
|||
|
|||
const open = ()=> { |
|||
show.value = true |
|||
} |
|||
|
|||
defineExpose({ |
|||
open |
|||
}) |
|||
</script> |
|||
|
|||
<style lang="scss" scoped></style> |
|||
@ -0,0 +1,117 @@ |
|||
<template> |
|||
<u-popup :show="show" :round="10" @close="show = false" :closeable="true"> |
|||
<view class="mx-[30rpx] pb-[20rpx] pt-[40rpx] border-t"> |
|||
<view class="text-base">{{ t('getAvatarNickname') }}</view> |
|||
<view class="text-sm mt-[18rpx] text-gray-400">{{ t('getAvatarNicknameTips') }}</view> |
|||
</view> |
|||
<u-form labelPosition="left" :model="formData" errorType='toast' :rules="rules" ref="formRef"> |
|||
<view class="mx-[30rpx]"> |
|||
<view class="mt-[20rpx]"> |
|||
<u-form-item :label="t('headimg')" prop="headimg" :border-bottom="true"> |
|||
<button class="m-0 my-[10rpx] p-0 w-[140rpx] h-[140rpx]" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"> |
|||
<view class="w-full h-full flex items-center justify-center overflow-hidden"> |
|||
<u-image :src="img(formData.headimg)" width="140rpx" height="140rpx" v-if="formData.headimg" mode="aspectFill"></u-image> |
|||
<u-icon name="plus" v-else></u-icon> |
|||
</view> |
|||
</button> |
|||
</u-form-item> |
|||
<u-form-item :label=" t('nickname')" prop="nickname" :border-bottom="true"> |
|||
<input type="nickname" v-model="formData.nickname" :placeholder="t('nicknamePlaceholder')" @blur="bindNickname"> |
|||
</u-form-item> |
|||
</view> |
|||
</view> |
|||
<view class="p-[30rpx] mt-[20rpx]"> |
|||
<u-button type="primary" :loading="loading" :text="t('confirm')" shape="circle" @click="confirm"></u-button> |
|||
</view> |
|||
</u-form> |
|||
</u-popup> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, reactive, computed, watch } from 'vue' |
|||
import { t } from '@/locale' |
|||
import useMemberStore from '@/stores/member' |
|||
import { img } from '@/utils/common' |
|||
import { modifyMember } from '@/app/api/member' |
|||
import { fetchBase64Image } from '@/app/api/system' |
|||
|
|||
const show = ref(false) |
|||
const loading = ref(false) |
|||
const memberStore = useMemberStore() |
|||
const info = computed(() => memberStore.info) |
|||
|
|||
const formData = reactive({ |
|||
nickname: '', |
|||
headimg: '' |
|||
}) |
|||
|
|||
watch(() => info.value, () => { |
|||
if (info.value) { |
|||
formData.nickname = info.value.nickname |
|||
formData.headimg = info.value.headimg |
|||
} |
|||
}, { immediate: true }) |
|||
|
|||
const onChooseAvatar = (e) => { |
|||
uni.getFileSystemManager().readFile({ |
|||
filePath: e.detail.avatarUrl, //选择图片返回的相对路径 |
|||
encoding: 'base64', //编码格式 |
|||
success: res => { |
|||
fetchBase64Image({ content: res.data }).then(uploadRes => { |
|||
formData.headimg = uploadRes.data.url |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const bindNickname = (e) => { |
|||
formData.nickname = e.detail.value |
|||
} |
|||
|
|||
const rules = { |
|||
'headimg': { |
|||
type: 'string', |
|||
required: true, |
|||
message: t('headimgPlaceholder'), |
|||
trigger: ['blur', 'change'], |
|||
}, |
|||
'nickname': { |
|||
type: 'string', |
|||
required: true, |
|||
message: t('nicknamePlaceholder'), |
|||
trigger: ['blur', 'change'], |
|||
} |
|||
} |
|||
|
|||
const formRef = ref(null) |
|||
|
|||
const confirm = async () => { |
|||
formRef.value.validate().then(async() => { |
|||
if (loading.value) return |
|||
loading.value = true |
|||
|
|||
// 修改头像 |
|||
await modifyMember({ field: 'headimg', value: formData.headimg }).then(() => { |
|||
memberStore.info.headimg = formData.headimg |
|||
}).catch(() => { |
|||
loading.value = false |
|||
}) |
|||
if (!loading.value) return |
|||
|
|||
// 修改昵称 |
|||
modifyMember({ field: 'nickname', value: formData.nickname }).then(() => { |
|||
memberStore.info.nickname = formData.nickname |
|||
loading.value = false |
|||
show.value = false |
|||
}).catch(() => { |
|||
loading.value = false |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
defineExpose({ |
|||
show |
|||
}) |
|||
</script> |
|||
|
|||
<style lang="scss" scoped></style> |
|||
@ -0,0 +1,22 @@ |
|||
const version = '3' |
|||
|
|||
// 开发环境才提示,生产环境不会提示
|
|||
if (process.env.NODE_ENV === 'development') { |
|||
console.log(`\n %c lxui-uni V${version} %c https://blog.csdn.net/qq_51091386/article/details/138125947 \n\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0;', 'color: #3c9cff;background: #ffffff; padding:5px 0;'); |
|||
} |
|||
|
|||
export let uploadUrl = '/api/v1.Resources/upload' |
|||
export let uploadBaseUrl = '' |
|||
const setConfig = (config: any) => { |
|||
console.log(config) |
|||
if (config?.uploadUrl) { |
|||
uploadUrl = config.uploadUrl |
|||
} |
|||
if (config?.uploadBaseUrl) { |
|||
uploadBaseUrl = config.uploadBaseUrl |
|||
} |
|||
} |
|||
export default { |
|||
version, |
|||
setConfig |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
/* |
|||
* @description: 分页请求 |
|||
* @fileName: useListLoadClass.ts |
|||
* @author: lxx |
|||
* @date: 2023-07-08 08:55:52 |
|||
* @version: V1.0.0 |
|||
*/ |
|||
import { reactive, ref, computed } from "vue" |
|||
import { onReachBottom } from "@dcloudio/uni-app" |
|||
|
|||
class LoadDataClass { |
|||
// 请求参数
|
|||
static queryParams = reactive({ |
|||
page: 1, |
|||
limit: 10 |
|||
}) |
|||
// 列表数据
|
|||
list = ref<any[]>([]) |
|||
total = ref(0) |
|||
// 前置处理方法
|
|||
afterLoadData: Function | undefined |
|||
// 请求方法
|
|||
Query: Function |
|||
// 加载状态参数
|
|||
isLoading = ref(false) |
|||
// 无更多数据了
|
|||
isNoData = computed(() => { |
|||
if (LoadDataClass.queryParams.page * LoadDataClass.queryParams.limit >= this.total.value) { |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
}) |
|||
// 显示暂无数据
|
|||
isEmpty = computed(() => { |
|||
if (this.total.value === 0) { |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
}) |
|||
|
|||
constructor(apiFunctions: Function, afterLoadData?: Function, options: any = {}) { |
|||
this.queryParamsReset() |
|||
this.Query = apiFunctions |
|||
this.afterLoadData = afterLoadData |
|||
// console.log('options', options)
|
|||
// 存在额外参数拼接
|
|||
this.setParams(options) |
|||
} |
|||
// 加载数据
|
|||
LoadData = async () => { |
|||
uni.showLoading({ |
|||
title: '加载中...' |
|||
}) |
|||
this.isLoading.value = true |
|||
const res = await this.Query(LoadDataClass.queryParams) |
|||
this.afterLoadData && this.afterLoadData(res) |
|||
this.total.value = res.data.total |
|||
this.list.value = this.list.value.concat(res.data.data) |
|||
|
|||
uni.hideLoading() |
|||
uni.stopPullDownRefresh() |
|||
this.isLoading.value = false |
|||
} |
|||
/** |
|||
* 添加额外参数刷新 |
|||
* @param options: 参数 |
|||
* @param isClear: 是否清空数据 false |
|||
*/ |
|||
setParams = (options: any, isClear: boolean = false) => { |
|||
if (isClear) { |
|||
this.queryParamsReset() |
|||
} else { |
|||
LoadDataClass.queryParams.page = 1 |
|||
} |
|||
this.list.value = [] |
|||
LoadDataClass.queryParams = Object.assign(LoadDataClass.queryParams, options) |
|||
// 加载数据
|
|||
this.LoadData() |
|||
} |
|||
// 加载更多
|
|||
LoadMore = () => { |
|||
if (this.isNoData.value || this.isLoading.value) return // 无数据或者加载中不进行加载
|
|||
LoadDataClass.queryParams.page += 1 |
|||
this.LoadData() |
|||
} |
|||
// 重置参数
|
|||
queryParamsReset = () => { |
|||
LoadDataClass.queryParams = reactive({ |
|||
page: 1, |
|||
limit: 10 |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* 刷新 |
|||
* @param isClear: 是否清空数据 |
|||
*/ |
|||
ReLoad = (isClear: boolean = false) => { |
|||
this.isLoading.value = false |
|||
this.list.value = [] |
|||
if (isClear) { |
|||
this.queryParamsReset() |
|||
} else { |
|||
LoadDataClass.queryParams.page = 1 |
|||
} |
|||
this.LoadData() |
|||
} |
|||
} |
|||
/** |
|||
* 分页加载数据方法 |
|||
* @param api: ListAPI 方法 |
|||
* @param afterLoadData: res数据前置处理方法 |
|||
* @returns |
|||
*/ |
|||
interface LoadDataInt { |
|||
api: Function |
|||
afterLoadData?: Function |
|||
options?: any |
|||
isNeedReachBottom?: boolean |
|||
} |
|||
|
|||
export function LoadData({ api, afterLoadData, options, isNeedReachBottom = true }: LoadDataInt) { |
|||
const data = new LoadDataClass(api, afterLoadData, options) |
|||
|
|||
// 下拉加载
|
|||
if (isNeedReachBottom) { |
|||
onReachBottom(() => { |
|||
console.log('onReachBottom') |
|||
data.LoadMore() |
|||
}) |
|||
} |
|||
|
|||
return { |
|||
list: data.list, |
|||
isLoading: data.isLoading, |
|||
isNoData: data.isNoData, |
|||
isEmpty: data.isEmpty, |
|||
ReLoad: data.ReLoad, |
|||
setParams: data.setParams, |
|||
LoadMore: data.LoadMore |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,294 @@ |
|||
/** |
|||
* 公共跳转方法 |
|||
* @author liux |
|||
* @date 2023-08-15 14:17 |
|||
* @param { string } url 跳转路径 |
|||
* @param { "navigateTo" | "redirectTo" | "reLaunch" | "switchTab" } [mode=navigateTo] 跳转模式 |
|||
* @param { object } params 跳转传参 |
|||
* @example |
|||
* goToPage({ url: 'pages/index/index', mode: 'navigateTo', params: {'id': 1} }) |
|||
* @returns { void } |
|||
*/ |
|||
type pageMode = 'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' |
|||
|
|||
interface goToPageInt { |
|||
url: string |
|||
mode?: pageMode |
|||
params?: { |
|||
[n: string]: string | number | boolean |
|||
} |
|||
} |
|||
|
|||
export const goToPage = ({ url, mode = 'navigateTo', params = {} }: goToPageInt): void => { |
|||
if (!url || url.length === 0) { |
|||
throw Error('"url" is a required parameter') |
|||
} |
|||
|
|||
const urlEncode = (params: any = {}) => { |
|||
const result :string[] = [] |
|||
for (const k in params) { |
|||
if (!params[k]) continue |
|||
result.push(k + '=' + params[k]) |
|||
} |
|||
|
|||
return result.join('&') |
|||
} |
|||
// const storage = JSON.parse(uni.getStorageSync('LX_user'))
|
|||
// const token = storage?.userInfo?.token
|
|||
// if(!token) {
|
|||
// url = 'pages/login/loginXcx'
|
|||
// mode = 'navigateTo'
|
|||
// }
|
|||
const queryStr = !isEmpty(params) ? '?' + urlEncode(params) : '' |
|||
const obj = { url: `/${url}${queryStr}` } |
|||
// console.log('obj', obj)
|
|||
switch (mode) { |
|||
case 'navigateTo': |
|||
uni.navigateTo(obj) |
|||
break |
|||
case 'redirectTo': |
|||
uni.redirectTo(obj) |
|||
break |
|||
case 'reLaunch': |
|||
uni.reLaunch(obj) |
|||
break |
|||
case 'switchTab': |
|||
uni.switchTab(obj) |
|||
break |
|||
default: |
|||
throw Error(`${mode} does not exist`) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 判断是否为空对象 |
|||
* @author liux |
|||
* @date 2023-08-15 14:17 |
|||
* @license MIT |
|||
* @param {*} object 源对象 |
|||
* @returns { boolean } |
|||
*/ |
|||
export const isEmptyObject = (object: any): boolean => { |
|||
return Object.keys(object).length === 0 |
|||
} |
|||
|
|||
/** |
|||
* 判断是否为对象 |
|||
* @author liux |
|||
* @date 2023-08-15 14:17 |
|||
* @license MIT |
|||
* @param {*} object 源对象 |
|||
* @returns { boolean } |
|||
*/ |
|||
export const isObject = (object: any): boolean => { |
|||
return Object.prototype.toString.call(object) === '[object Object]' |
|||
} |
|||
|
|||
/** |
|||
* 判断是否为数组 |
|||
* @author liux |
|||
* @license MIT |
|||
* @param {*} object 源对象 |
|||
* @returns { boolean } |
|||
*/ |
|||
export const isArray = (object: any): boolean => { |
|||
return Object.prototype.toString.call(object) === '[object Array]' |
|||
} |
|||
|
|||
/** |
|||
* 判断是否为空 |
|||
* @author liux |
|||
* @license MIT |
|||
* @param {*} value 源对象 |
|||
* @returns { boolean } |
|||
*/ |
|||
export const isEmpty = (value: any): boolean => { |
|||
if (isArray(value)) { |
|||
return value.length === 0 |
|||
} |
|||
if (isObject(value)) { |
|||
return isEmptyObject(value) |
|||
} |
|||
return !value |
|||
} |
|||
|
|||
/** |
|||
* 格式化时间戳(多格式) |
|||
* @author liux |
|||
* @license MIT |
|||
* @param { number } time 长度为 10 | 13 的时间戳 |
|||
* @param { string } [format=yyyy-MM-dd] format 转换格式 |
|||
* @example |
|||
* formatTime(1691744378556, 'yyyy-MM-dd HH:mm:ss') |
|||
* @returns { string } |
|||
*/ |
|||
|
|||
export const formatTime = (time: number, format: string = 'yyyy-MM-dd HH:mm:ss'): string => { |
|||
const len = time.toString().trim().length |
|||
if (len !== 10 && len !== 13) { |
|||
throw Error('"time" is a error parameter') |
|||
} |
|||
|
|||
time = len !== 13 ? time * 1000 : time |
|||
|
|||
if (!time) return '' |
|||
const date = new Date(time) |
|||
const M = (date.getMonth() + 1).toString() |
|||
const d = date.getDate().toString() |
|||
const H = date.getHours().toString() |
|||
const m = date.getMinutes().toString() |
|||
const s = date.getSeconds().toString() |
|||
const timeObject: { |
|||
[n: string]: string |
|||
} = { |
|||
yyyy: date.getFullYear().toString(), |
|||
MM: M.padStart(2, '0'), |
|||
dd: d.padStart(2, '0'), |
|||
HH: H.padStart(2, '0'), |
|||
mm: m.padStart(2, '0'), |
|||
ss: s.padStart(2, '0'), |
|||
M: M, |
|||
d: d, |
|||
H: H, |
|||
m: m, |
|||
s: s |
|||
} |
|||
const reg = new RegExp(Object.keys(timeObject).join('|'), 'g') |
|||
const res = format.replace(reg, (k) => { |
|||
return timeObject[k] |
|||
}) |
|||
return res |
|||
} |
|||
|
|||
// 判断当前时间是否在一个时间区间内
|
|||
export const isTimeIn = (start: string, end: string) => { |
|||
// 获取当前时间
|
|||
const currentTime = new Date() |
|||
const startArr = start.split(':').map(Number) |
|||
// 设置开始时间为10:00
|
|||
const startTime = new Date() |
|||
startTime.setHours(startArr[0], startArr[1]) |
|||
|
|||
// 设置结束时间为20:00
|
|||
const endArr = end.split(':').map(Number) |
|||
const endTime = new Date() |
|||
endTime.setHours(endArr[0], endArr[1]) |
|||
|
|||
// 检查当前时间是否在10:00到20:00之间
|
|||
if (currentTime >= startTime && currentTime <= endTime) { |
|||
// console.log('当前时间在之间')
|
|||
} else { |
|||
// console.log('当前时间不在之间')
|
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 防抖函数 |
|||
* @author liux |
|||
* @license MIT |
|||
* @param {function} fn |
|||
* @param {umber} [wait=1000] wait |
|||
* @returns { void } |
|||
*/ |
|||
export const debounce = <T extends (...args: any[]) => any>(fn: T, wait: number = 1000): ((...args: Parameters<T>) => void) => { |
|||
let timer: any |
|||
|
|||
return function (this: any, ...args: Parameters<T>) { |
|||
if (timer) clearTimeout(timer) |
|||
|
|||
timer = setTimeout(() => { |
|||
fn.apply(this, args) |
|||
}, wait) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 节流函数 |
|||
* @author liux |
|||
* @date 2023-08-15 14:17 |
|||
* @license MIT |
|||
* @param { function } fn |
|||
* @param { number } [wait=1000] wait |
|||
*/ |
|||
export const throttle = <T extends (...args: any[]) => any>(fn: T, wait: number = 1000) => { |
|||
let timer: number = Date.now() |
|||
return function (this: any, ...args: Parameters<T>) { |
|||
if (Date.now() - timer >= wait) { |
|||
fn.apply(this, args) |
|||
timer = Date.now() |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 保存图片到本地 |
|||
* @author liux |
|||
* @param { string } url 需要下载的图片 |
|||
* @example |
|||
* saveImgData('/upload/images/img.png') |
|||
* @returns |
|||
*/ |
|||
export const saveImgData = debounce((url: string) => { |
|||
uni.showLoading({ title: '图片保存中...', mask: true }) |
|||
// 判断图片地址是否有http
|
|||
if (url.indexOf('http') === -1) { |
|||
url = import.meta.env.VITE_APP_BASE_URL + url |
|||
} |
|||
uni.downloadFile({ |
|||
url, |
|||
success: (res: any) => { |
|||
if (res.statusCode === 200) { |
|||
uni.saveImageToPhotosAlbum({ |
|||
filePath: res.tempFilePath, |
|||
success: () => { |
|||
uni.showToast({ |
|||
title: '保存成功~', |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}) |
|||
}, |
|||
fail: () => { |
|||
uni.showToast({ |
|||
title: '保存失败~', |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}) |
|||
}, |
|||
complete: () => { |
|||
uni.hideLoading() |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
}) |
|||
|
|||
/** |
|||
* 设置剪贴板 |
|||
* @author liux |
|||
* @param { string } data 需要复制的内容 |
|||
* @example |
|||
* setClipboardData('123456') |
|||
* @returns |
|||
*/ |
|||
export const setClipboardData = (data: string) => { |
|||
uni.setClipboardData({ |
|||
data: data, |
|||
success: () => { |
|||
uni.showToast({ |
|||
title: '复制成功', |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
export default { |
|||
goToPage, |
|||
formatTime, |
|||
debounce, |
|||
throttle, |
|||
saveImgData, |
|||
setClipboardData |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
<template> |
|||
<view class="zcks-card"> |
|||
<view class="craditem" v-for="(item,index) in newCheckOptions" :key="index" :class="{'active': item.selected}" |
|||
:style="{ '--color': activeColor }" @click="selectData(index,item)"> |
|||
{{item.sku_name}} |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
//选项数据 |
|||
checkOptions: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
//选择类型(单选:single,默认复选) |
|||
checkType: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
//复选时最大可选数量(类型为单选的时不生效) |
|||
maxNum: { |
|||
type: Number, |
|||
}, |
|||
//主题色 |
|||
activeColor: { |
|||
type: String, |
|||
default: '#0A4B9D' |
|||
} |
|||
}, |
|||
watch: { |
|||
checkOptions: { |
|||
deep: true, |
|||
immediate: true, |
|||
handler(parentArry) { |
|||
this.newCheckOptions = parentArry |
|||
}, |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
newCheckOptions: [] |
|||
} |
|||
}, |
|||
methods: { |
|||
selectData(index, item) { |
|||
if (this.checkType == 'single') { //单选 |
|||
this.newCheckOptions.forEach(mess => { |
|||
mess.selected = false |
|||
}) |
|||
this.$set(item, 'selected', true) |
|||
} else { |
|||
let chooseData = this.newCheckOptions.filter(mes => mes.selected) |
|||
if (this.maxNum && !item.selected && chooseData.length >= this.maxNum) { |
|||
uni.showToast({ |
|||
title: '最多可选' + this.maxNum + '项', |
|||
icon: 'none' |
|||
}) |
|||
return |
|||
} |
|||
this.$set(item, 'selected', item.selected ? false : true) |
|||
} |
|||
this.newCheckOptions = JSON.parse(JSON.stringify(this.newCheckOptions)) |
|||
let chooseOnData = this.newCheckOptions.filter(mes => mes.selected) |
|||
this.$emit("checkChange", chooseOnData) |
|||
}, |
|||
//重置 |
|||
reset() { |
|||
this.newCheckOptions.forEach(mess => { |
|||
mess.selected = false |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.zcks-card { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: flex-start; |
|||
flex-wrap: wrap; |
|||
} |
|||
|
|||
.craditem { |
|||
padding: 8rpx 24rpx; |
|||
margin-right: 24rpx; |
|||
margin-bottom: 16rpx; |
|||
background: #FFFFFF; |
|||
border-radius: 4rpx; |
|||
border: 2rpx solid #999999; |
|||
font-size: 30rpx; |
|||
font-weight: 400; |
|||
color: #333333; |
|||
line-height: 48rpx; |
|||
text-align: left; |
|||
} |
|||
|
|||
.active { |
|||
border: 2rpx solid var(--color); |
|||
color: var(--color); |
|||
position: relative; |
|||
} |
|||
|
|||
.active::after { |
|||
content: ""; |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
width: 0; |
|||
height: 0; |
|||
border-top: 42rpx solid var(--color); |
|||
border-left: 42rpx solid transparent; |
|||
} |
|||
|
|||
.active::before { |
|||
width: 8rpx; |
|||
height: 12rpx; |
|||
border-color: #FFFFFF; |
|||
border-style: solid; |
|||
border-width: 0 1rpx 2rpx 0; |
|||
content: ""; |
|||
position: absolute; |
|||
right: 8rpx; |
|||
top: 2rpx; |
|||
z-index: 3; |
|||
transform: rotate(45deg); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,249 @@ |
|||
<script lang="ts" setup> |
|||
/* |
|||
* @description: 头部组件 |
|||
* @fileName: Header.vue |
|||
* @params |
|||
* @author: lxx |
|||
* @date: 2023-07-16 09:32:09 |
|||
* @version: V1.0.2 |
|||
*/ |
|||
import { goToPage } from '../libs/util' |
|||
import { type CSSProperties, computed, ref, watchEffect } from 'vue' |
|||
|
|||
type headerInt = { |
|||
title: string |
|||
// 头部高度 默认为44px (微信小程序不可用) |
|||
headerHeight?: number |
|||
// 是否显示左侧内容 |
|||
leftIconShow?: boolean |
|||
// 样式部分 |
|||
backgroundColor?: string |
|||
backgroundColor2?: string |
|||
textColor?: string |
|||
textFontSize?: number |
|||
// 是否需要生成和头部高度相同的盒子 |
|||
isShowHeaderBox?: boolean |
|||
positionState?: string |
|||
isShowShadow?: boolean |
|||
isBlackIcon?: boolean |
|||
// 头部没有右侧盒子,true:左中右结构 false:左中结构(小程序可以为左中右结构但是没有右侧盒子) |
|||
isHaveRightBox?: boolean |
|||
} |
|||
|
|||
const props = withDefaults(defineProps<headerInt>(), { |
|||
// 头部高度 默认为44px (微信小程序不可用) |
|||
headerHeight: 44, |
|||
// 是否显示左侧内容 |
|||
leftIconShow: true, |
|||
// 样式部分 |
|||
backgroundColor: '#ffffff', //linear-gradient(90deg, rgba(10, 207, 254, 1) 0%, rgba(74, 92, 255, 1) 100%) |
|||
backgroundColor2: '#ffffff', |
|||
textColor: '#000', |
|||
textFontSize: 34, |
|||
title: '标题', |
|||
// 是否需要生成和头部高度相同的盒子 |
|||
isShowHeaderBox: true, |
|||
positionState: 'fixed', |
|||
isShowShadow: false, |
|||
isBlackIcon: true, // 是否为黑色图标 |
|||
isHaveRightBox: true |
|||
}) |
|||
|
|||
let { statusBarHeight } = uni.getSystemInfoSync() |
|||
// #ifdef MP-WEIXIN |
|||
// 胶囊状态 |
|||
let menuButton = uni.getMenuButtonBoundingClientRect() |
|||
// 微信头部宽度 |
|||
let wxHeaderWidth = menuButton.left - 10 |
|||
// 上边距 |
|||
statusBarHeight = menuButton.top |
|||
// #endif |
|||
|
|||
// padding的高度(防止头部塌陷) |
|||
const fillBoxHeight = ref(statusBarHeight + 3) |
|||
|
|||
// 设置header的高度 |
|||
const headerHeightRef = ref(0) |
|||
watchEffect(() => { |
|||
headerHeightRef.value = props.headerHeight |
|||
// #ifdef MP-WEIXIN |
|||
// 中间高度 |
|||
headerHeightRef.value = menuButton.height |
|||
// #endif |
|||
}) |
|||
// console.log('statusBarHeight', statusBarHeight) |
|||
|
|||
const style = computed(() => { |
|||
return { |
|||
boxShadow: props.isShowShadow ? '0 0 8rpx -3rpx #333' : '0 0 0 0 #333', |
|||
background: props.backgroundColor, |
|||
color: props.textColor, |
|||
position: props.positionState |
|||
} as CSSProperties |
|||
}) |
|||
// 返回上一页(如没有页面返回首页) |
|||
const goBack = () => { |
|||
if (getCurrentPages().length <= 1) { |
|||
goToPage({ |
|||
url: 'pages/index/index', |
|||
mode: 'redirectTo' |
|||
}) |
|||
} else { |
|||
uni.navigateBack() |
|||
} |
|||
} |
|||
|
|||
const backIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAACMklEQVR4nO2av0uVURjHPyiEYgkuoVC4ODSK2pRDYFOENOQP6A+IEgknkWgKgqTEP6AaHIKyhqhGiSgVpKwGBweLBMHZcjAK47xcXrxxn6el5/pwzvnAme5wv5/ve+/hPO/7kslkMplMxjsjwCdgB7iV2tW6Aez/tW46yFUXpmvIh/U6AXfuCvJhnXOQz5R7ivz1iL0LZhX5Sw7ymTIviG8DpyP2pg14Kch/BjodZDSjHdgQ5D8CrZF6F3QD3wT5VeCog4xm9ADfBfn3scv3KvJhL2hxkNGMsJv/EOQfRepcchbYFeQnnWQ046JywLkSqXPJlCJ/2UlGM7Rz/WikziX3BfFfwLCTjGY8FuR/Av2ROpdIQ004159yktGMZ4L8Yuzn+mZgQZB/BTQ6yGhGOLevCPIvInUu6QK+CPLPnWQ05Z0gPxuxcxXSIWfAUUZT5oQCfgPXIvau4onyS5hwlNOUB0oJcxF7V6ENPuFofMRRVjNuKyW8SaWEIaWEt0CTg4zmDCq3v5ZSKUG7AboMdDjIaE6fUsJX4ETk/gU9lddbapWwncLzfiqPwTaVzfGCg4zmdCiTYxJ3iPnHo/CwrjrIWBeeKiWMJeBfoM0P4w7y1YUZpYQ7CfgXaEPUtIN8dUF6KXK/cqu9IYEOincApRIWU5kfxpUS1oDjDjKacx7YE0rYSGWI6lXmh4f/60s8bywfKkPUVo3Pjh1CnkPjJLB+4OqHv8aZlArIZDKZTMYC4A8EQ0uQY7/3uAAAAABJRU5ErkJggg==' |
|||
const backIconW = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAACjElEQVR4nO2azatNURiH364S+SgTuUUmBoa6GDEwuCNJBr7KHyAkGUkyUsoN3T8AA0r5GugyUZK4lHwODAwQpYwvBqQeve4+deK8a5+zz9q3d6+9nnonp7PXWb/n7L1aH1symUwmk8lUBpiL2gO8BmaAU/p7bpiD8Cf4n5NtETDRI7zywEH0WWoMf9YIr4x7yP6XmsKfC4Q/kvoYMBkIv7PzPTdEDn/DCP4V2Nj93dQELAPuGOHfAKv/vcYNEcKvAN4b4V8BS3tdl4qAdcAnI/xLYLF1rRuGCD8GfDPCPw+FT0HA+kB4HQsWlbXhhgrhdTT/boS/2m87TRWwBfhhhD82SFtuGKDTO4zgyv5B76SmCTgeCL+vyjjihj46G5rX7604iDZGwAUj+G9gd9XwTRFwzQj/C9g8TPgmCLAWNTqvXztseO8Cbhnhp615fSoCFgL3jfB3gXmxwnsUoPP2Z0b4qZjBPQpYA3wwwt+uI7w3AY+N8JN1hY8pYCRCG5uMz6citF07MQRcMT6/JyIHHWWth+KWvG48BsrR1MeATl0MSLjcBgFSsvDRqfH81AVonQ5IeBhLghuMDu4KSHgELEhdgNb2wPbXk2EluKGko6EN0KfAaOoCtDYEJHwEVqYuQIpDkBlDgh58jqcuQIpjsM+BwXFb6gKkeOatlSOD7BC7ocLzGzoKVw6kLqBTNwMSDrVBgJSsHw63QYDW+YCEM20QICWLqIk2CJDAS5EUW+0jqQuQ4h1Ai+nu9YMbIguQYvCzeAssT12A1lbgpyFB3ygbdRB9lpoESPEekbV+uBSr/zF2hevihYiMiciXHu0vcdPLGu+ATq0C3nX9+/poWGcRmUwmk8lk+kRE/gCfWLdyj0KPNgAAAABJRU5ErkJggg==' |
|||
</script> |
|||
|
|||
<template> |
|||
<!-- #ifndef MP-TOUTIAO --> |
|||
<view class="header_box"> |
|||
<view class="header_main" :style="style"> |
|||
<view class="status_bar" :style="{ height: statusBarHeight + 'px' }"></view> |
|||
<!-- 标准的左中右结构 --> |
|||
<view v-if="isHaveRightBox" class="header flex-center-between" :style="{ height: headerHeightRef + 'px' }"> |
|||
<view class="header_left flex-center-between"> |
|||
<slot name="left"> |
|||
<view v-if="leftIconShow" class="icon flex" @click="goBack"> |
|||
<image :src="isBlackIcon ? backIcon : backIconW" mode="widthFix"></image> |
|||
</view> |
|||
</slot> |
|||
</view> |
|||
<view class="header_center"> |
|||
<slot> |
|||
<view class="title" :style="{ fontSize: textFontSize + 'rpx' }"> |
|||
{{ title }} |
|||
</view> |
|||
</slot> |
|||
</view> |
|||
<view class="header_right flex"> |
|||
<!-- #ifndef MP-WEIXIN --> |
|||
<slot name="right"></slot> |
|||
<!-- #endif --> |
|||
</view> |
|||
</view> |
|||
<!-- 左右结构 --> |
|||
<view v-else class="wx_header flex" |
|||
:style="{ height: headerHeightRef + 'px', width: wxHeaderWidth + 'px' }"> |
|||
<view class="wx_header_left flex"> |
|||
<slot name="left"> |
|||
<view class="icon flex" @click="goBack" v-if="leftIconShow"> |
|||
<image :src="isBlackIcon ? backIcon : backIconW" mode="widthFix"></image> |
|||
</view> |
|||
</slot> |
|||
</view> |
|||
<view class="wx_header_txt flex"> |
|||
<slot name="center"> |
|||
<view class="title" :style="{ fontSize: textFontSize + 'rpx' }"> |
|||
{{ title }} |
|||
</view> |
|||
</slot> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<!-- 填充头部防止塌陷 新加(v-if="isShowHeaderBox") --> |
|||
<view class="status_bar" v-if="isShowHeaderBox" :style="{ height: fillBoxHeight + 'px' }"></view> |
|||
<!-- #ifdef MP-WEIXIN --> |
|||
<view v-if="isShowHeaderBox" :style="{ height: headerHeightRef + 4 + 'px', background: backgroundColor2 }"> |
|||
</view> |
|||
<!-- #endif --> |
|||
<!-- 待测试 --> |
|||
<!-- #ifndef MP-WEIXIN --> |
|||
<view v-if="isShowHeaderBox" :style="{ height: headerHeightRef + 'px', background: backgroundColor2 }"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
<!-- #endif --> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped> |
|||
.header_box { |
|||
// padding: 0 20rpx 5rpx; |
|||
// background-color: #fff; |
|||
} |
|||
|
|||
.header_main { |
|||
width: 100%; |
|||
z-index: 9999; |
|||
top: 0; |
|||
left: 0; |
|||
/* #ifdef MP-WEIXIN */ |
|||
padding: 0 20rpx 15rpx; |
|||
/* #endif */ |
|||
/* #ifndef MP-WEIXIN */ |
|||
padding: 5rpx 20rpx; |
|||
/* #endif */ |
|||
box-sizing: border-box; |
|||
|
|||
.img { |
|||
width: 48rpx; |
|||
height: 48rpx; |
|||
} |
|||
|
|||
.header { |
|||
// padding: 0 16rpx; |
|||
box-sizing: border-box; |
|||
|
|||
.header_left { |
|||
width: 20%; |
|||
|
|||
.icon { |
|||
width: 48rpx; |
|||
} |
|||
|
|||
.left_txt { |
|||
font-size: 22rpx; |
|||
line-height: 22rpx; |
|||
} |
|||
} |
|||
|
|||
.header_center { |
|||
width: 60%; |
|||
text-align: center; |
|||
font-size: 28rpx; |
|||
|
|||
.title { |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
} |
|||
|
|||
.header_right { |
|||
width: 20%; |
|||
} |
|||
} |
|||
|
|||
.wx_header { |
|||
.wx_header_left { |
|||
height: 100%; |
|||
|
|||
.icon { |
|||
width: 48rpx; |
|||
|
|||
image { |
|||
vertical-align: middle; |
|||
width: 100%; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.wx_header_txt { |
|||
flex: 1; |
|||
height: 100%; |
|||
padding-left: 26rpx; |
|||
|
|||
.title { |
|||
width: 100%; |
|||
line-height: 1; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
font-size: 28rpx; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
../../libs/util |
|||
@ -0,0 +1,35 @@ |
|||
<template> |
|||
<image :src="imgUrl" :style="{ 'width': width, 'height': height, 'border-radius': round }" :mode="mode" /> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed } from 'vue'; |
|||
|
|||
interface propInt { |
|||
imgUrl: string |
|||
mode: string |
|||
width: string |
|||
height: string |
|||
round: string |
|||
} |
|||
|
|||
const props = withDefaults(defineProps<propInt>(), { |
|||
// 图片路径 |
|||
src: '', |
|||
// 图片模式 |
|||
mode: '', |
|||
// 图片宽 |
|||
width: '100%', |
|||
// 图片高 |
|||
height: '100%', |
|||
// 圆角 |
|||
round: '' |
|||
}) |
|||
|
|||
const imgUrl = computed(() => { |
|||
return import.meta.env.VITE_IMG_DOMAIN + props.src |
|||
}) |
|||
|
|||
</script> |
|||
|
|||
<style lang="scss" scoped></style> |
|||
File diff suppressed because one or more lines are too long
@ -0,0 +1,102 @@ |
|||
<script setup lang="ts"> |
|||
/* |
|||
* @description: 分页组件 |
|||
* @fileName: List.vue |
|||
* @params {Function} api : 数据请求的接口API |
|||
* @params {Function} afterLoadData : 数据请求完毕前置处理方法 |
|||
* @params {object: any} options : 数据请求的额外参数 |
|||
* @params {'default' | 'scrollView'} listType : scrollView为scroll-view包裹的分页 |
|||
* @ref setListParams {Function} : 使用==>ref.value.setListParams(obj, isClear) obj:请求额外的参数 isClear是否清空请求参数 |
|||
* @author: lxx |
|||
* @date: 2023-07-28 11:36:23 |
|||
* @update: 2023-10-13 10:34:03 |
|||
* @version: V1.0.1 |
|||
*/ |
|||
import { LoadData } from '../libs/hooks/useListLoadClass' |
|||
import { toRefs, ref } from 'vue'; |
|||
import lxListState from '../lx-list-state/lx-list-state.vue' |
|||
interface listPropsInt { |
|||
api: Function |
|||
afterLoadData?: Function |
|||
options?: any |
|||
listType?: 'default' | 'scrollView' |
|||
} |
|||
|
|||
// 接收传值 |
|||
// 3.2.x defineProps泛型类型参数仅限于类型文字或对本地接口的引用 |
|||
const props = withDefaults(defineProps<listPropsInt>(), { |
|||
api: () => ({}), |
|||
// eslint-disable-next-line @typescript-eslint/no-empty-function |
|||
options: {}, |
|||
listType: 'default' |
|||
}) |
|||
let { options, api, afterLoadData } = toRefs(props) |
|||
// console.log('props', props.options, options.value) |
|||
|
|||
// 分页相关 |
|||
let { list, isLoading, isEmpty, isNoData, setParams, LoadMore } = LoadData({ |
|||
api: api.value, |
|||
afterLoadData: afterLoadData?.value, |
|||
options: options.value |
|||
}) |
|||
// 搜索相关 |
|||
// const inputTxt = ref('') |
|||
// const inputChange = debounce(() => { |
|||
// console.log('input change') |
|||
// setParams({ search: inputTxt.value }) |
|||
// }) |
|||
|
|||
// 用于父组件设置额外的参数(选项卡切换的时候使用)--> isClear true 清空列表需测试 |
|||
const setListParams = (obj: any, isClear = true) => { |
|||
const param = ref<{ |
|||
search?: string |
|||
[key: string]: any |
|||
}>({}) |
|||
// if (props.isNeedSearch) { |
|||
// param.value.search = inputTxt.value |
|||
// } |
|||
param.value = { ...obj } |
|||
setParams({ ...param.value }, isClear) |
|||
} |
|||
defineExpose({ |
|||
list, |
|||
setListParams, |
|||
LoadMore |
|||
}) |
|||
// 页面滚动相关 |
|||
const scrollTop = ref(0) |
|||
const scroll = (e: any) => { |
|||
scrollTop.value = e.detail.scrollTop |
|||
} |
|||
|
|||
const emit = defineEmits(['scrollTolower']) |
|||
const scrolltolower = () => { |
|||
console.log('触底啦!') |
|||
LoadMore() |
|||
emit('scrollTolower') |
|||
} |
|||
</script> |
|||
<template> |
|||
<template v-if="listType == 'default'"> |
|||
<view class="lxx_list_box"> |
|||
<view class="lxx_list_box_content"> |
|||
<template v-for="(item, index) in list" :key="index"> |
|||
<slot v-bind:item="item" v-bind:index="index"></slot> |
|||
</template> |
|||
</view> |
|||
<lxListState :is-empty="isEmpty" :is-loading="isLoading" :is-no-data="isNoData"></lxListState> |
|||
</view> |
|||
</template> |
|||
|
|||
<template v-else> |
|||
<scroll-view scroll-y="true" class="scroll-Y" @scroll="scroll" @scrolltolower="scrolltolower"> |
|||
<view class="lxx_list_box"> |
|||
<view v-for="(item, index) in list" :key="index"> |
|||
<slot v-bind:item="item" v-bind:index="index"></slot> |
|||
</view> |
|||
</view> |
|||
<lxListState :is-empty="isEmpty" :is-loading="isLoading" :is-no-data="isNoData"></lxListState> |
|||
</scroll-view> |
|||
</template> |
|||
</template> |
|||
<style lang="scss" scoped></style> |
|||
@ -0,0 +1,66 @@ |
|||
// 小程序无法在hook中使用页面级别生命周期,需单独传入: https://ask.dcloud.net.cn/question/161173
|
|||
// import { onPageScroll, onReachBottom, onPullDownRefresh} from '@dcloudio/uni-app';
|
|||
|
|||
/** |
|||
* 初始化mescroll, 相当于vue2的mescroll-mixins.js文件 (mescroll-body 和 mescroll-uni 通用) |
|||
* mescroll-body需传入onPageScroll, onReachBottom |
|||
* mescroll-uni无需传onPageScroll, onReachBottom |
|||
* 当down.native为true时,需传入onPullDownRefresh |
|||
*/ |
|||
function useMescroll(onPageScroll, onReachBottom, onPullDownRefresh){ |
|||
// mescroll实例对象
|
|||
let mescroll = null; |
|||
|
|||
// mescroll组件初始化的回调,可获取到mescroll对象
|
|||
const mescrollInit = (e)=> { |
|||
mescroll = e; |
|||
} |
|||
|
|||
// 获取mescroll对象, mescrollInit执行之后会有值, 生命周期created中会有值
|
|||
const getMescroll = ()=>{ |
|||
return mescroll |
|||
} |
|||
|
|||
// 下拉刷新的回调 (mixin默认resetUpScroll)
|
|||
const downCallback = ()=> { |
|||
if(mescroll.optUp.use){ |
|||
mescroll.resetUpScroll() |
|||
}else{ |
|||
setTimeout(()=>{ |
|||
mescroll.endSuccess(); |
|||
}, 500) |
|||
} |
|||
} |
|||
|
|||
// 上拉加载的回调
|
|||
const upCallback = ()=> { |
|||
// mixin默认延时500自动结束加载
|
|||
setTimeout(()=>{ |
|||
mescroll.endErr(); |
|||
}, 500) |
|||
} |
|||
|
|||
// 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
onPullDownRefresh && onPullDownRefresh(() => { |
|||
mescroll && mescroll.onPullDownRefresh(); |
|||
}) |
|||
|
|||
// 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
|
|||
onPageScroll && onPageScroll(e=>{ |
|||
mescroll && mescroll.onPageScroll(e); |
|||
}) |
|||
|
|||
// 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
|
|||
onReachBottom && onReachBottom(()=>{ |
|||
mescroll && mescroll.onReachBottom(); |
|||
}) |
|||
|
|||
return { |
|||
getMescroll, |
|||
mescrollInit, |
|||
downCallback, |
|||
upCallback |
|||
} |
|||
} |
|||
|
|||
export default useMescroll |
|||
@ -0,0 +1,56 @@ |
|||
import { ref } from 'vue'; |
|||
|
|||
// 小程序无法在hook中使用页面级别生命周期,需单独传入: https://ask.dcloud.net.cn/question/161173
|
|||
// import { onPageScroll, onReachBottom, onPullDownRefresh} from '@dcloudio/uni-app';
|
|||
|
|||
/** |
|||
* mescroll-body写在子组件时,需通过useMescrollComp补充子组件缺少的生命周期, 相当于vue2的mescroll-comp.js文件 |
|||
* 必须传入onPageScroll, onReachBottom |
|||
* 当down.native为true时,需传入onPullDownRefresh |
|||
*/ |
|||
function useMescrollComp(onPageScroll, onReachBottom, onPullDownRefresh){ |
|||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
|
|||
onPageScroll(e=>{ |
|||
handlePageScroll(e) |
|||
}) |
|||
|
|||
onReachBottom(()=>{ |
|||
handleReachBottom() |
|||
}) |
|||
|
|||
// 当down的native: true时, 还需传递此方法进到子组件
|
|||
onPullDownRefresh && onPullDownRefresh(()=>{ |
|||
handlePullDownRefresh() |
|||
}) |
|||
|
|||
const mescrollItem = ref(null) |
|||
|
|||
const handlePageScroll = (e)=>{ |
|||
const mescroll = getMescroll() |
|||
mescroll && mescroll.onPageScroll(e); |
|||
} |
|||
|
|||
const handleReachBottom = ()=>{ |
|||
const mescroll = getMescroll() |
|||
mescroll && mescroll.onReachBottom(); |
|||
} |
|||
|
|||
const handlePullDownRefresh = ()=>{ |
|||
const mescroll = getMescroll() |
|||
mescroll && mescroll.onPullDownRefresh(); |
|||
} |
|||
|
|||
const getMescroll = ()=>{ |
|||
if(mescrollItem.value && mescrollItem.value.getMescroll){ |
|||
return mescrollItem.value.getMescroll() |
|||
} |
|||
return null |
|||
} |
|||
|
|||
return { |
|||
mescrollItem, |
|||
getMescroll |
|||
} |
|||
} |
|||
|
|||
export default useMescrollComp |
|||
@ -0,0 +1,69 @@ |
|||
import { ref } from 'vue'; |
|||
|
|||
// 小程序无法在hook中使用页面级别生命周期,需单独传入: https://ask.dcloud.net.cn/question/161173
|
|||
// import { onPageScroll, onReachBottom, onPullDownRefresh} from '@dcloudio/uni-app';
|
|||
|
|||
/** mescroll-more示例写在子组件时,需通过useMescrollMore补充子组件缺少的生命周期, 相当于vue2的mescroll-more.js文件 */ |
|||
function useMescrollMore(mescrollItems, onPageScroll, onReachBottom, onPullDownRefresh){ |
|||
// 当前tab下标
|
|||
const tabIndex = ref(0) |
|||
|
|||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
|
|||
onPageScroll && onPageScroll(e=>{ |
|||
handlePageScroll(e) |
|||
}) |
|||
|
|||
onReachBottom && onReachBottom(()=>{ |
|||
handleReachBottom() |
|||
}) |
|||
|
|||
// 当down的native: true时, 还需传递此方法进到子组件
|
|||
onPullDownRefresh && onPullDownRefresh(()=>{ |
|||
handlePullDownRefresh() |
|||
}) |
|||
|
|||
const handlePageScroll = (e)=>{ |
|||
let mescroll = getMescroll(tabIndex.value); |
|||
mescroll && mescroll.onPageScroll(e); |
|||
} |
|||
const handleReachBottom = ()=>{ |
|||
let mescroll = getMescroll(tabIndex.value); |
|||
mescroll && mescroll.onReachBottom(); |
|||
} |
|||
|
|||
const handlePullDownRefresh = ()=>{ |
|||
let mescroll = getMescroll(tabIndex.value); |
|||
mescroll && mescroll.onPullDownRefresh(); |
|||
} |
|||
|
|||
// 根据下标获取对应子组件的mescroll
|
|||
const getMescroll = (i)=>{ |
|||
if (mescrollItems && mescrollItems[i]) { |
|||
return mescrollItems[i].value.getMescroll() |
|||
} else{ |
|||
return null |
|||
} |
|||
} |
|||
|
|||
// 切换tab,恢复滚动条位置
|
|||
const scrollToLastY = ()=>{ |
|||
let mescroll = getMescroll(tabIndex.value); |
|||
if(mescroll){ |
|||
// 恢复上次滚动条的位置
|
|||
let y = mescroll.getScrollTop() |
|||
mescroll.scrollTo(y, 0) |
|||
// 再次恢复上次滚动条的位置, 确保元素已渲染
|
|||
setTimeout(()=>{ |
|||
mescroll.scrollTo(y, 0) |
|||
},20) |
|||
} |
|||
} |
|||
|
|||
return { |
|||
tabIndex, |
|||
getMescroll, |
|||
scrollToLastY |
|||
} |
|||
} |
|||
|
|||
export default useMescrollMore |
|||
@ -0,0 +1,19 @@ |
|||
.mescroll-body { |
|||
position: relative; /* 下拉刷新区域相对自身定位 */ |
|||
height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/ |
|||
overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */ |
|||
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */ |
|||
} |
|||
|
|||
/* 使sticky生效: 父元素不能overflow:hidden或者overflow:auto属性 */ |
|||
.mescroll-body.mescorll-sticky{ |
|||
overflow: unset !important |
|||
} |
|||
|
|||
/* 适配 iPhoneX */ |
|||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { |
|||
.mescroll-safearea { |
|||
padding-bottom: constant(safe-area-inset-bottom); |
|||
padding-bottom: env(safe-area-inset-bottom); |
|||
} |
|||
} |
|||
@ -0,0 +1,400 @@ |
|||
<template> |
|||
<view |
|||
class="mescroll-body mescroll-render-touch" |
|||
:class="{'mescorll-sticky': sticky}" |
|||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" |
|||
@touchstart="wxsBiz.touchstartEvent" |
|||
@touchmove="wxsBiz.touchmoveEvent" |
|||
@touchend="wxsBiz.touchendEvent" |
|||
@touchcancel="wxsBiz.touchendEvent" |
|||
:change:prop="wxsBiz.propObserver" |
|||
:prop="wxsProp" |
|||
> |
|||
<!-- 状态栏 --> |
|||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view> |
|||
|
|||
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view class="downwarp-progress mescroll-wxs-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 && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="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> |
|||
|
|||
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) --> |
|||
<!-- #ifdef H5 --> |
|||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> |
|||
<!-- #endif --> |
|||
|
|||
<!-- 适配iPhoneX --> |
|||
<view v-if="safearea" class="mescroll-safearea"></view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
|
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 微信小程序, QQ小程序, app, h5使用wxs --> |
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<script src="../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script> |
|||
<!-- #endif --> |
|||
|
|||
<!-- app, h5使用renderjs --> |
|||
<!-- #ifdef APP-PLUS || H5 --> |
|||
<script module="renderBiz" lang="renderjs"> |
|||
import renderBiz from "../mescroll-uni/wxs/renderjs.js"; |
|||
export default { |
|||
mixins: [renderBiz] |
|||
} |
|||
</script> |
|||
<!-- #endif --> |
|||
|
|||
<script> |
|||
// 引入mescroll-uni.js,处理核心逻辑 |
|||
import MeScroll from "../mescroll-uni/mescroll-uni.js"; |
|||
// 引入全局配置 |
|||
import GlobalOption from "../mescroll-uni/mescroll-uni-option.js"; |
|||
// 引入国际化工具类 |
|||
import mescrollI18n from '../mescroll-uni/mescroll-i18n.js'; |
|||
// 引入回到顶部组件 |
|||
import MescrollTop from "../mescroll-uni/components/mescroll-top.vue"; |
|||
// 引入兼容wxs(含renderjs)写法的mixins |
|||
import WxsMixin from "../mescroll-uni/wxs/mixins.js"; |
|||
|
|||
/** |
|||
* mescroll-body 基于page滚动的下拉刷新和上拉加载组件, 支持嵌套原生组件, 性能好 |
|||
* @property {Object} down 下拉刷新的参数配置 |
|||
* @property {Object} up 上拉加载的参数配置 |
|||
* @property {Object} i18n 国际化的参数配置 |
|||
* @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
* @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|||
* @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
* @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
* @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true |
|||
* @property {String, Number} height 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
* @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效) |
|||
* @property {Boolean} sticky 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法隐藏 |
|||
* @event {Function} init 初始化完成的回调 |
|||
* @event {Function} down 下拉刷新的回调 |
|||
* @event {Function} up 上拉加载的回调 |
|||
* @event {Function} emptyclick 点击empty配置的btnText按钮回调 |
|||
* @event {Function} topclick 点击回到顶部的按钮回调 |
|||
* @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效) |
|||
* @example <mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-body> |
|||
*/ |
|||
export default { |
|||
name: 'mescroll-body', |
|||
mixins: [WxsMixin], |
|||
components: { |
|||
MescrollTop |
|||
}, |
|||
props: { |
|||
down: Object, |
|||
up: Object, |
|||
i18n: Object, |
|||
top: [String, Number], |
|||
topbar: [Boolean, String], |
|||
bottom: [String, Number], |
|||
safearea: Boolean, |
|||
height: [String, Number], |
|||
bottombar:{ |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
sticky: Boolean |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: {optDown:{},optUp:{}}, // mescroll实例 |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1) |
|||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
statusBarHeight: 0 // 状态栏高度 |
|||
}; |
|||
}, |
|||
computed: { |
|||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
minHeight(){ |
|||
return this.toPx(this.height || '100%') + 'px' |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) |
|||
}, |
|||
padTop() { |
|||
return this.numTop + 'px'; |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom); |
|||
}, |
|||
padBottom() { |
|||
return this.numBottom + 'px'; |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
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(){ |
|||
if(!this.mescroll) return ""; // 避免头条小程序初始化时报错 |
|||
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.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset; |
|||
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; |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
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() { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset() { |
|||
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组件时,此行不可删) |
|||
}, |
|||
beforeEndDownScroll(mescroll){ |
|||
vm.downLoadType = 4; |
|||
return mescroll.optDown.beforeEndDelay // 延时结束的时长 |
|||
}, |
|||
endDownScroll() { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时 |
|||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset |
|||
if(vm.downLoadType === 4) vm.downLoadType = 0 |
|||
},300) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll); |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll(mescroll) { |
|||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
let i18nType = mescrollI18n.getType() // 当前语言类型 |
|||
let i18nOption = {type: i18nType} // 国际化配置 |
|||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置 |
|||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置 |
|||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置 |
|||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置 |
|||
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为滚动区域 |
|||
// 挂载语言包 |
|||
vm.mescroll.i18n = i18nOption; |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
|
|||
// 因为使用的是page的scroll,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
if(typeof y === 'string'){ |
|||
// 滚动到指定view (y为css选择器) |
|||
setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick |
|||
let selector; |
|||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){ |
|||
selector = '#'+y // 不带#和. 则默认为id选择器 |
|||
}else{ |
|||
selector = y |
|||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK |
|||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询) |
|||
selector = y.split('>>>')[1].trim() |
|||
} |
|||
// #endif |
|||
} |
|||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){ |
|||
if (rect) { |
|||
let top = rect.top |
|||
top += vm.mescroll.getScrollTop() |
|||
uni.pageScrollTo({ |
|||
scrollTop: top, |
|||
duration: t |
|||
}) |
|||
} else{ |
|||
console.error(selector + ' does not exist'); |
|||
} |
|||
}).exec() |
|||
},30) |
|||
} else{ |
|||
// 滚动到指定位置 (y必须为数字) |
|||
uni.pageScrollTo({ |
|||
scrollTop: y, |
|||
duration: t |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
|
|||
// 全局配置监听 |
|||
uni.$on("setMescrollGlobalOption", options=>{ |
|||
if(!options) return; |
|||
let i18nType = options.i18n ? options.i18n.type : null |
|||
if(i18nType && vm.mescroll.i18n.type != i18nType){ |
|||
vm.mescroll.i18n.type = i18nType |
|||
mescrollI18n.setType(i18nType) |
|||
MeScroll.extend(options, vm.mescroll.i18n[i18nType]) |
|||
} |
|||
if(options.down){ |
|||
let down = MeScroll.extend({}, options.down) |
|||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown) |
|||
} |
|||
if(options.up){ |
|||
let up = MeScroll.extend({}, options.up) |
|||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp) |
|||
} |
|||
}) |
|||
}, |
|||
destroyed() { |
|||
// 注销全局配置监听 |
|||
uni.$off("setMescrollGlobalOption") |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../mescroll-body/mescroll-body.css"; |
|||
@import "../mescroll-uni/components/mescroll-down.css"; |
|||
@import "../mescroll-uni/components/mescroll-up.css"; |
|||
</style> |
|||
@ -0,0 +1,47 @@ |
|||
/*下拉刷新--标语*/ |
|||
.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(https://www.mescroll.com/img/beibei/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(https://www.mescroll.com/img/beibei/mescroll-bb1.png)} |
|||
25% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb2.png)} |
|||
50% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb3.png)} |
|||
75% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb4.png)} |
|||
100% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<!-- 下拉刷新区域 --> |
|||
<template> |
|||
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/> |
|||
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view> |
|||
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view> |
|||
<view class="downwarp-mascot"></view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
option: Object , // down的配置项 |
|||
type: Number // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4) |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption(){ |
|||
return this.option || {} |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading(){ |
|||
return this.type === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate(){ |
|||
return this.type === 2 ? 'rotate(180deg)' : 'rotate(0deg)' |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../../../mescroll-uni/components/mescroll-down.css"; |
|||
@import "./mescroll-down.css"; |
|||
</style> |
|||
@ -0,0 +1,360 @@ |
|||
<template> |
|||
<view |
|||
class="mescroll-body mescroll-render-touch" |
|||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" |
|||
:class="{'mescorll-sticky': sticky}" |
|||
@touchstart="wxsBiz.touchstartEvent" |
|||
@touchmove="wxsBiz.touchmoveEvent" |
|||
@touchend="wxsBiz.touchendEvent" |
|||
@touchcancel="wxsBiz.touchendEvent" |
|||
:change:prop="wxsBiz.propObserver" |
|||
:prop="wxsProp" |
|||
> |
|||
|
|||
<!-- 状态栏 --> |
|||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view> |
|||
|
|||
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/> |
|||
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view> |
|||
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view> |
|||
<view class="downwarp-mascot"></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 && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="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> |
|||
|
|||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) --> |
|||
<!-- #ifdef H5 --> |
|||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> |
|||
<!-- #endif --> |
|||
|
|||
<!-- 适配iPhoneX --> |
|||
<view v-if="safearea" class="mescroll-safearea"></view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
|
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 微信小程序, QQ小程序, app, h5使用wxs --> |
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script> |
|||
<!-- #endif --> |
|||
|
|||
<!-- app, h5使用renderjs --> |
|||
<!-- #ifdef APP-PLUS || H5 --> |
|||
<script module="renderBiz" lang="renderjs"> |
|||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js'; |
|||
export default { |
|||
mixins: [renderBiz] |
|||
} |
|||
</script> |
|||
<!-- #endif --> |
|||
|
|||
<script> |
|||
import MeScroll from '../../mescroll-uni/mescroll-uni.js'; |
|||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue'; |
|||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js'; |
|||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js'; |
|||
import GlobalOption from './mescroll-uni-option.js'; |
|||
|
|||
export default { |
|||
mixins: [WxsMixin], |
|||
components: { |
|||
MescrollTop |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: null, // mescroll实例 |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
statusBarHeight: 0 // 状态栏高度 |
|||
}; |
|||
}, |
|||
props: { |
|||
down: Object, // 下拉刷新的参数配置 |
|||
up: Object, // 上拉加载的参数配置 |
|||
i18n: Object, // 国际化的参数配置 |
|||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
sticky: Boolean // 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法会隐藏 |
|||
}, |
|||
computed: { |
|||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
minHeight(){ |
|||
return this.toPx(this.height || '100%') + 'px' |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) |
|||
}, |
|||
padTop() { |
|||
return this.numTop + 'px'; |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom); |
|||
}, |
|||
padBottom() { |
|||
return this.numBottom + 'px'; |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
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 this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)' |
|||
} |
|||
}, |
|||
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; |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
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() { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset() { |
|||
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() { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时 |
|||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset |
|||
if(vm.downLoadType === 4) vm.downLoadType = 0 |
|||
},300) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll); |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll(mescroll) { |
|||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
let i18nType = mescrollI18n.getType() // 当前语言类型 |
|||
let i18nOption = {type: i18nType} // 国际化配置 |
|||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置 |
|||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置 |
|||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置 |
|||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置 |
|||
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为滚动区域 |
|||
// 挂载语言包 |
|||
vm.mescroll.i18n = i18nOption; |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
|
|||
// 因为使用的是page的scroll,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
if(typeof y === 'string'){ |
|||
// 滚动到指定view (y为css选择器) |
|||
setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick |
|||
let selector; |
|||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){ |
|||
selector = '#'+y // 不带#和. 则默认为id选择器 |
|||
}else{ |
|||
selector = y |
|||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK |
|||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询) |
|||
selector = y.split('>>>')[1].trim() |
|||
} |
|||
// #endif |
|||
} |
|||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){ |
|||
if (rect) { |
|||
let top = rect.top |
|||
top += vm.mescroll.getScrollTop() |
|||
uni.pageScrollTo({ |
|||
scrollTop: top, |
|||
duration: t |
|||
}) |
|||
} else{ |
|||
console.error(selector + ' does not exist'); |
|||
} |
|||
}).exec() |
|||
},30) |
|||
} else{ |
|||
// 滚动到指定位置 (y必须为数字) |
|||
uni.pageScrollTo({ |
|||
scrollTop: y, |
|||
duration: t |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
|
|||
// 全局配置监听 |
|||
uni.$on("setMescrollGlobalOption", options=>{ |
|||
if(!options) return; |
|||
let i18nType = options.i18n ? options.i18n.type : null |
|||
if(i18nType && vm.mescroll.i18n.type != i18nType){ |
|||
vm.mescroll.i18n.type = i18nType |
|||
mescrollI18n.setType(i18nType) |
|||
MeScroll.extend(options, vm.mescroll.i18n[i18nType]) |
|||
} |
|||
if(options.down){ |
|||
let down = MeScroll.extend({}, options.down) |
|||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown) |
|||
} |
|||
if(options.up){ |
|||
let up = MeScroll.extend({}, options.up) |
|||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp) |
|||
} |
|||
}) |
|||
}, |
|||
destroyed() { |
|||
// 注销全局配置监听 |
|||
uni.$off("setMescrollGlobalOption") |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../../mescroll-body/mescroll-body.css"; |
|||
@import "../../mescroll-uni/components/mescroll-down.css"; |
|||
@import "../../mescroll-uni/components/mescroll-up.css"; |
|||
@import "./components/mescroll-down.css"; |
|||
</style> |
|||
@ -0,0 +1,51 @@ |
|||
import { img } from '@/utils/common'; |
|||
|
|||
// mescroll-uni和mescroll-body 的全局配置
|
|||
const GlobalOption = { |
|||
down: { |
|||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
|||
offset: uni.upx2px(140), // 在列表顶部,下拉大于140upx,松手即可触发下拉刷新的回调
|
|||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
}, |
|||
up: { |
|||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
|||
offset: 150, // 距底部多远时,触发upCallback
|
|||
toTop: { |
|||
// 回到顶部按钮,需配置src才显示
|
|||
src: "https://www.mescroll.com/img/mescroll-totop.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: img("static/resource/images/system/empty.png") // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
|||
} |
|||
}, |
|||
// 国际化配置
|
|||
i18n: { |
|||
// 中文
|
|||
zh: { |
|||
up: { |
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textNoMore: '', // 没有更多数据的提示文本
|
|||
empty: { |
|||
tip: '~ 暂无相关数据 ~' // 空提示
|
|||
} |
|||
} |
|||
}, |
|||
// 英文
|
|||
en: { |
|||
up: { |
|||
textLoading: 'loading ...', |
|||
textNoMore: '', |
|||
empty: { |
|||
tip: '~ absolutely empty ~' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default GlobalOption |
|||
@ -0,0 +1,437 @@ |
|||
<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,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false"> |
|||
<view class="mescroll-uni-content mescroll-render-touch" |
|||
@touchstart="wxsBiz.touchstartEvent" |
|||
@touchmove="wxsBiz.touchmoveEvent" |
|||
@touchend="wxsBiz.touchendEvent" |
|||
@touchcancel="wxsBiz.touchendEvent" |
|||
:change:prop="wxsBiz.propObserver" |
|||
:prop="wxsProp"> |
|||
|
|||
<!-- 状态栏 --> |
|||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view> |
|||
|
|||
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/> |
|||
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view> |
|||
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view> |
|||
<view class="downwarp-mascot"></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 && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="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> |
|||
|
|||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) --> |
|||
<!-- #ifdef H5 --> |
|||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> |
|||
<!-- #endif --> |
|||
|
|||
<!-- 适配iPhoneX --> |
|||
<view v-if="safearea" class="mescroll-safearea"></view> |
|||
</view> |
|||
</scroll-view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
|
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 微信小程序, QQ小程序, app, h5使用wxs --> |
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script> |
|||
<!-- #endif --> |
|||
|
|||
<!-- app, h5使用renderjs --> |
|||
<!-- #ifdef APP-PLUS || H5 --> |
|||
<script module="renderBiz" lang="renderjs"> |
|||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js'; |
|||
export default { |
|||
mixins: [renderBiz] |
|||
} |
|||
</script> |
|||
<!-- #endif --> |
|||
|
|||
<script> |
|||
import MeScroll from '../../mescroll-uni/mescroll-uni.js'; |
|||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue'; |
|||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js'; |
|||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js'; |
|||
import GlobalOption from './mescroll-uni-option.js'; |
|||
|
|||
export default { |
|||
mixins: [WxsMixin], |
|||
components: { |
|||
MescrollTop |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: null, // mescroll实例 |
|||
viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素) |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|||
upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
scrollTop: 0, // 滚动条的位置 |
|||
scrollAnim: false, // 是否开启滚动动画 |
|||
windowTop: 0, // 可使用窗口的顶部位置 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
statusBarHeight: 0 // 状态栏高度 |
|||
} |
|||
}, |
|||
props: { |
|||
down: Object, // 下拉刷新的参数配置 |
|||
up: Object, // 上拉加载的参数配置 |
|||
i18n: Object, // 国际化的参数配置 |
|||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
fixed: { // 是否通过fixed固定mescroll的高度, 默认true |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
disableScroll: Boolean // 是否禁止滚动 |
|||
}, |
|||
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) |
|||
}, |
|||
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 |
|||
}, |
|||
padBottom() { |
|||
return !this.isFixed ? this.numBottom + 'px' : 0 |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
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之外 |
|||
}, |
|||
// 列表是否可滑动 |
|||
scrollable(){ |
|||
if(this.disableScroll) return false |
|||
return this.downLoadType===0 || this.isDownReset |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading(){ |
|||
return this.downLoadType === 3 |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate(){ |
|||
return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)' |
|||
} |
|||
}, |
|||
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获取是否向上滑动 |
|||
}) |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
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已渲染 |
|||
this.getClientInfo(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) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
}, |
|||
// 获取滚动区域的信息 |
|||
getClientInfo(success){ |
|||
let query = uni.createSelectorQuery(); |
|||
// #ifndef MP-ALIPAY || MP-DINGTALK |
|||
query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值 |
|||
// #endif |
|||
let view = query.select('#' + this.viewId); |
|||
view.boundingClientRect(data => { |
|||
success(data) |
|||
}).exec(); |
|||
} |
|||
}, |
|||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|||
created() { |
|||
let vm = this; |
|||
|
|||
let diyOption = { |
|||
// 下拉刷新的配置 |
|||
down: { |
|||
inOffset() { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset() { |
|||
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() { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
vm.downResetTimer && clearTimeout(vm.downResetTimer) |
|||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整 |
|||
if(vm.downLoadType===4) vm.downLoadType = 0 |
|||
},300) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll) |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll(mescroll) { |
|||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { // 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { // 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
// 更新容器的高度 (多mescroll的情况) |
|||
vm.setClientHeight() |
|||
} |
|||
} |
|||
} |
|||
|
|||
let i18nType = mescrollI18n.getType() // 当前语言类型 |
|||
let i18nOption = {type: i18nType} // 国际化配置 |
|||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置 |
|||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置 |
|||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置 |
|||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置 |
|||
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 |
|||
// 挂载语言包 |
|||
vm.mescroll.i18n = i18nOption; |
|||
// 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'){ |
|||
// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现 |
|||
vm.getClientInfo(function(rect){ |
|||
let mescrollTop = rect.top // mescroll到顶部的距离 |
|||
let selector; |
|||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){ |
|||
selector = '#'+y // 不带#和. 则默认为id选择器 |
|||
}else{ |
|||
selector = y |
|||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK |
|||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询) |
|||
selector = y.split('>>>')[1].trim() |
|||
} |
|||
// #endif |
|||
} |
|||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){ |
|||
if (rect) { |
|||
let curY = vm.mescroll.getScrollTop() |
|||
let top = rect.top - mescrollTop |
|||
top += curY |
|||
if(!vm.isFixed) top -= vm.numTop |
|||
vm.scrollTop = curY; |
|||
vm.$nextTick(function() { |
|||
vm.scrollTop = top |
|||
}) |
|||
} else{ |
|||
console.error(selector + ' does not exist'); |
|||
} |
|||
}).exec() |
|||
}) |
|||
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 (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
// 全局配置监听 |
|||
uni.$on("setMescrollGlobalOption", options=>{ |
|||
if(!options) return; |
|||
let i18nType = options.i18n ? options.i18n.type : null |
|||
if(i18nType && vm.mescroll.i18n.type != i18nType){ |
|||
vm.mescroll.i18n.type = i18nType |
|||
mescrollI18n.setType(i18nType) |
|||
MeScroll.extend(options, vm.mescroll.i18n[i18nType]) |
|||
} |
|||
if(options.down){ |
|||
let down = MeScroll.extend({}, options.down) |
|||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown) |
|||
} |
|||
if(options.up){ |
|||
let up = MeScroll.extend({}, options.up) |
|||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp) |
|||
} |
|||
}) |
|||
}, |
|||
mounted() { |
|||
// 设置容器的高度 |
|||
this.setClientHeight() |
|||
}, |
|||
destroyed() { |
|||
// 注销全局配置监听 |
|||
uni.$off("setMescrollGlobalOption") |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../../mescroll-uni/mescroll-uni.css"; |
|||
@import "../../mescroll-uni/components/mescroll-down.css"; |
|||
@import "../../mescroll-uni/components/mescroll-up.css"; |
|||
@import "./components/mescroll-down.css"; |
|||
</style> |
|||
@ -0,0 +1,44 @@ |
|||
/*下拉刷新--上下箭头*/ |
|||
.mescroll-downwarp .downwarp-arrow { |
|||
display: inline-block; |
|||
width: 20px; |
|||
height: 20px; |
|||
margin: 10px; |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-arrow.png); |
|||
background-size: contain; |
|||
vertical-align: middle; |
|||
transition: all 300ms; |
|||
} |
|||
|
|||
/*下拉刷新--旋转进度条*/ |
|||
.mescroll-downwarp .downwarp-progress{ |
|||
width: 36px; |
|||
height: 36px; |
|||
border: none; |
|||
margin: auto; |
|||
background-size: contain; |
|||
animation: progressRotate 0.6s steps(6, start) infinite; |
|||
} |
|||
@keyframes progressRotate { |
|||
0% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png); |
|||
} |
|||
16% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png); |
|||
} |
|||
32% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png); |
|||
} |
|||
48% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png); |
|||
} |
|||
64% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png); |
|||
} |
|||
80% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png); |
|||
} |
|||
100% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png); |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
<!-- 下拉刷新区域 --> |
|||
<template> |
|||
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view v-if="isDownLoading" class="downwarp-progress"></view> |
|||
<view v-else class="downwarp-arrow" :style="{ 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) |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption() { |
|||
return this.option || {}; |
|||
}, |
|||
// 是否在加载中 |
|||
isDownLoading() { |
|||
return this.type === 3; |
|||
}, |
|||
// 旋转的角度 |
|||
downRotate() { |
|||
return this.type === 2 ? 'rotate(-180deg)' : 'rotate(0deg)'; |
|||
}, |
|||
// 文本提示 |
|||
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-uni/components/mescroll-down.css'; |
|||
@import './mescroll-down.css'; |
|||
</style> |
|||
@ -0,0 +1,32 @@ |
|||
/*上拉加载--旋转进度条*/ |
|||
.mescroll-upwarp .upwarp-progress { |
|||
width: 36px; |
|||
height: 36px; |
|||
border: none; |
|||
margin: auto; |
|||
background-size: contain; |
|||
animation: progressRotate 0.6s steps(6, start) infinite; |
|||
} |
|||
@keyframes progressRotate { |
|||
0% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png); |
|||
} |
|||
16% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png); |
|||
} |
|||
32% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png); |
|||
} |
|||
48% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png); |
|||
} |
|||
64% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png); |
|||
} |
|||
80% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png); |
|||
} |
|||
100% { |
|||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
<!-- 上拉加载区域 --> |
|||
<template> |
|||
<view class="mescroll-upwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="isUpLoading"> |
|||
<view class="upwarp-progress mescroll-rotate"></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(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示) |
|||
}, |
|||
computed: { |
|||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|||
mOption() { |
|||
return this.option || {}; |
|||
}, |
|||
// 加载中 |
|||
isUpLoading() { |
|||
return this.type === 1; |
|||
}, |
|||
// 没有更多了 |
|||
isUpNoMore() { |
|||
return this.type === 2; |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import '../../../mescroll-uni/components/mescroll-up.css'; |
|||
@import './mescroll-up.css'; |
|||
</style> |
|||
@ -0,0 +1,380 @@ |
|||
<template> |
|||
<view |
|||
class="mescroll-body mescroll-render-touch" |
|||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" |
|||
:class="{'mescorll-sticky': sticky}" |
|||
@touchstart="wxsBiz.touchstartEvent" |
|||
@touchmove="wxsBiz.touchmoveEvent" |
|||
@touchend="wxsBiz.touchendEvent" |
|||
@touchcancel="wxsBiz.touchendEvent" |
|||
:change:prop="wxsBiz.propObserver" |
|||
:prop="wxsProp" |
|||
> |
|||
|
|||
<!-- 状态栏 --> |
|||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view> |
|||
|
|||
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp"> |
|||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)--> |
|||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> --> |
|||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}"> |
|||
<view class="downwarp-content"> |
|||
<view v-if="isDownLoading" class="downwarp-progress"></view> |
|||
<view v-else class="downwarp-arrow" :style="{ 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 && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> --> |
|||
<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}"> |
|||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|||
<view v-show="upLoadType===1"> |
|||
<view class="upwarp-progress mescroll-rotate"></view> |
|||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view> |
|||
</view> |
|||
<!-- 无数据 --> |
|||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) --> |
|||
<!-- #ifdef H5 --> |
|||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view> |
|||
<!-- #endif --> |
|||
|
|||
<!-- 适配iPhoneX --> |
|||
<view v-if="safearea" class="mescroll-safearea"></view> |
|||
|
|||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)--> |
|||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top> |
|||
|
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<!-- 微信小程序, QQ小程序, app, h5使用wxs --> |
|||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 --> |
|||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script> |
|||
<!-- #endif --> |
|||
|
|||
<!-- app, h5使用renderjs --> |
|||
<!-- #ifdef APP-PLUS || H5 --> |
|||
<script module="renderBiz" lang="renderjs"> |
|||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js'; |
|||
export default { |
|||
mixins: [renderBiz] |
|||
} |
|||
</script> |
|||
<!-- #endif --> |
|||
|
|||
<script> |
|||
import MeScroll from '../../mescroll-uni/mescroll-uni.js'; |
|||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue'; |
|||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js'; |
|||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js'; |
|||
import GlobalOption from './mescroll-uni-option.js'; |
|||
|
|||
export default { |
|||
mixins: [WxsMixin], |
|||
components: { |
|||
MescrollTop |
|||
}, |
|||
data() { |
|||
return { |
|||
mescroll: null, // mescroll实例 |
|||
downHight: 0, //下拉刷新: 容器高度 |
|||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示) |
|||
isShowEmpty: false, // 是否显示空布局 |
|||
isShowToTop: false, // 是否显示回到顶部按钮 |
|||
windowHeight: 0, // 可使用窗口的高度 |
|||
windowBottom: 0, // 可使用窗口的底部位置 |
|||
statusBarHeight: 0 // 状态栏高度 |
|||
}; |
|||
}, |
|||
props: { |
|||
down: Object, // 下拉刷新的参数配置 |
|||
up: Object, // 上拉加载的参数配置 |
|||
i18n: Object, // 国际化的参数配置 |
|||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|||
height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
sticky: Boolean // 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法会隐藏 |
|||
}, |
|||
computed: { |
|||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|||
minHeight(){ |
|||
return this.toPx(this.height || '100%') + 'px' |
|||
}, |
|||
// 下拉布局往下偏移的距离 (px) |
|||
numTop() { |
|||
return this.toPx(this.top) |
|||
}, |
|||
padTop() { |
|||
return this.numTop + 'px'; |
|||
}, |
|||
// 上拉布局往上偏移 (px) |
|||
numBottom() { |
|||
return this.toPx(this.bottom); |
|||
}, |
|||
padBottom() { |
|||
return this.numBottom + 'px'; |
|||
}, |
|||
// 是否为重置下拉的状态 |
|||
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 this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)'; |
|||
}, |
|||
// 文本提示 |
|||
downText() { |
|||
if(!this.mescroll) return ""; |
|||
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.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset; |
|||
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; |
|||
}, |
|||
// 点击空布局的按钮回调 |
|||
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() { |
|||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
outOffset() { |
|||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
onMoving(mescroll, rate, downHight) { |
|||
// 下拉过程中的回调,滑动过程一直在执行; |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
showLoading(mescroll, downHight) { |
|||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
}, |
|||
beforeEndDownScroll(mescroll){ |
|||
vm.downLoadType = 4; |
|||
return mescroll.optDown.beforeEndDelay // 延时结束的时长 |
|||
}, |
|||
endDownScroll() { |
|||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|||
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时 |
|||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset |
|||
if(vm.downLoadType === 4) vm.downLoadType = 0 |
|||
},300) |
|||
}, |
|||
// 派发下拉刷新的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('down', mescroll); |
|||
} |
|||
}, |
|||
// 上拉加载的配置 |
|||
up: { |
|||
// 显示加载中的回调 |
|||
showLoading() { |
|||
vm.upLoadType = 1; |
|||
}, |
|||
// 显示无更多数据的回调 |
|||
showNoMore() { |
|||
vm.upLoadType = 2; |
|||
}, |
|||
// 隐藏上拉加载的回调 |
|||
hideUpScroll(mescroll) { |
|||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3; |
|||
}, |
|||
// 空布局 |
|||
empty: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowEmpty = isShow; |
|||
} |
|||
}, |
|||
// 回到顶部 |
|||
toTop: { |
|||
onShow(isShow) { |
|||
// 显示隐藏的回调 |
|||
vm.isShowToTop = isShow; |
|||
} |
|||
}, |
|||
// 派发上拉加载的回调 |
|||
callback: function(mescroll) { |
|||
vm.$emit('up', mescroll); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
let i18nType = mescrollI18n.getType() // 当前语言类型 |
|||
let i18nOption = {type: i18nType} // 国际化配置 |
|||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置 |
|||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置 |
|||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置 |
|||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置 |
|||
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为滚动区域 |
|||
// 挂载语言包 |
|||
vm.mescroll.i18n = i18nOption; |
|||
// init回调mescroll对象 |
|||
vm.$emit('init', vm.mescroll); |
|||
|
|||
// 设置高度 |
|||
const sys = uni.getSystemInfoSync(); |
|||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|||
// 使down的bottomOffset生效 |
|||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|||
|
|||
// 因为使用的是page的scroll,这里需自定义scrollTo |
|||
vm.mescroll.resetScrollTo((y, t) => { |
|||
if(typeof y === 'string'){ |
|||
// 滚动到指定view (y为css选择器) |
|||
setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick |
|||
let selector; |
|||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){ |
|||
selector = '#'+y // 不带#和. 则默认为id选择器 |
|||
}else{ |
|||
selector = y |
|||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK |
|||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询) |
|||
selector = y.split('>>>')[1].trim() |
|||
} |
|||
// #endif |
|||
} |
|||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){ |
|||
if (rect) { |
|||
let top = rect.top |
|||
top += vm.mescroll.getScrollTop() |
|||
uni.pageScrollTo({ |
|||
scrollTop: top, |
|||
duration: t |
|||
}) |
|||
} else{ |
|||
console.error(selector + ' does not exist'); |
|||
} |
|||
}).exec() |
|||
},30) |
|||
} else{ |
|||
// 滚动到指定位置 (y必须为数字) |
|||
uni.pageScrollTo({ |
|||
scrollTop: y, |
|||
duration: t |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|||
} |
|||
|
|||
// 全局配置监听 |
|||
uni.$on("setMescrollGlobalOption", options=>{ |
|||
if(!options) return; |
|||
let i18nType = options.i18n ? options.i18n.type : null |
|||
if(i18nType && vm.mescroll.i18n.type != i18nType){ |
|||
vm.mescroll.i18n.type = i18nType |
|||
mescrollI18n.setType(i18nType) |
|||
MeScroll.extend(options, vm.mescroll.i18n[i18nType]) |
|||
} |
|||
if(options.down){ |
|||
let down = MeScroll.extend({}, options.down) |
|||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown) |
|||
} |
|||
if(options.up){ |
|||
let up = MeScroll.extend({}, options.up) |
|||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp) |
|||
} |
|||
}) |
|||
}, |
|||
destroyed() { |
|||
// 注销全局配置监听 |
|||
uni.$off("setMescrollGlobalOption") |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style> |
|||
@import "../../mescroll-uni/mescroll-uni.css"; |
|||
@import "../../mescroll-uni/components/mescroll-down.css"; |
|||
@import "../../mescroll-uni/components/mescroll-up.css"; |
|||
@import "./components/mescroll-down.css"; |
|||
@import "./components/mescroll-up.css"; |
|||
</style> |
|||
@ -0,0 +1,66 @@ |
|||
import { img } from '@/utils/common'; |
|||
|
|||
// 全局配置
|
|||
// mescroll-body 和 mescroll-uni 通用
|
|||
const GlobalOption = { |
|||
down: { |
|||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
|||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
|||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|||
}, |
|||
up: { |
|||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
|||
offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
|
|||
toTop: { |
|||
// 回到顶部按钮,需配置src才显示
|
|||
src: "https://www.mescroll.com/img/mescroll-totop.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: img("static/resource/images/system/empty.png") // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
|||
} |
|||
}, |
|||
// 国际化配置
|
|||
i18n: { |
|||
// 中文
|
|||
zh: { |
|||
down: { |
|||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
|||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textSuccess: '加载成功', // 加载成功的文本
|
|||
textErr: '加载失败', // 加载失败的文本
|
|||
}, |
|||
up: { |
|||
textLoading: '加载中 ...', // 加载中的提示文本
|
|||
textNoMore: '', // 没有更多数据的提示文本
|
|||
empty: { |
|||
tip: '暂无相关数据' // 空提示
|
|||
} |
|||
} |
|||
}, |
|||
// 英文
|
|||
en: { |
|||
down: { |
|||
textInOffset: 'drop down refresh', |
|||
textOutOffset: 'release updates', |
|||
textLoading: 'loading ...', |
|||
textSuccess: 'loaded successfully', |
|||
textErr: 'loading failed' |
|||
}, |
|||
up: { |
|||
textLoading: 'loading ...', |
|||
textNoMore: '', |
|||
empty: { |
|||
tip: '~ absolutely empty ~' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default GlobalOption |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue