@ -0,0 +1,11 @@ |
||||
# 页面标题 |
||||
VITE_APP_TITLE = 金武联驾校 |
||||
|
||||
# 开发环境配置 |
||||
VITE_APP_ENV = 'development' |
||||
|
||||
# 金武联驾校/开发环境 |
||||
VITE_APP_BASE_API = 'https://test.jd-mall.cn/dev/zongheng-api/' |
||||
|
||||
# |
||||
VITE_WEB_BASE_URL = 'https://test.jd-mall.cn/dev/zongheng' |
||||
@ -0,0 +1,11 @@ |
||||
# 页面标题 |
||||
VITE_APP_TITLE = 金武联驾校 |
||||
|
||||
# 开发环境配置 |
||||
VITE_APP_ENV = 'production' |
||||
|
||||
# 金武联驾校/开发环境 |
||||
VITE_APP_BASE_API = 'https://i.equ-tech.com/zongheng-api/' |
||||
|
||||
# |
||||
VITE_WEB_BASE_URL = 'https://i.equ-tech.com/zongheng' |
||||
@ -0,0 +1,23 @@ |
||||
.DS_Store |
||||
node_modules/ |
||||
dist/ |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
**/*.log |
||||
|
||||
tests/**/coverage/ |
||||
tests/e2e/reports |
||||
selenium-debug.log |
||||
|
||||
# Editor directories and files |
||||
.idea |
||||
.vscode |
||||
*.suo |
||||
*.ntvs* |
||||
*.njsproj |
||||
*.sln |
||||
*.local |
||||
|
||||
package-lock.json |
||||
yarn.lock |
||||
@ -0,0 +1,20 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8" /> |
||||
<script> |
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || |
||||
CSS.supports('top: constant(a)')) |
||||
document.write( |
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + |
||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />') |
||||
</script> |
||||
<title></title> |
||||
<!--preload-links--> |
||||
<!--app-context--> |
||||
</head> |
||||
<body> |
||||
<div id="app"><!--app-html--></div> |
||||
<script type="module" src="/src/main.js"></script> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,9 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"types": [ |
||||
"@dcloudio/types", |
||||
"miniprogram-api-typings", |
||||
"mini-types" |
||||
] |
||||
} |
||||
} |
||||
@ -0,0 +1,33 @@ |
||||
{ |
||||
"name": "OA", |
||||
"version": "0.0.0", |
||||
"scripts": { |
||||
"dev:h5": "uni", |
||||
"dev": "uni -p mp-weixin", |
||||
"build:h5": "uni build", |
||||
"build": "uni build -p mp-weixin", |
||||
"build-test:mp-weixin": "uni --mode test -p mp-weixin" |
||||
}, |
||||
"dependencies": { |
||||
"@dcloudio/uni-app": "3.0.0-alpha-3060420220922001", |
||||
"@dcloudio/uni-app-plus": "3.0.0-alpha-3060420220922001", |
||||
"@dcloudio/uni-components": "3.0.0-alpha-3060420220922001", |
||||
"@dcloudio/uni-h5": "3.0.0-alpha-3060420220922001", |
||||
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-3060420220922001", |
||||
"jsencrypt-plus": "^0.1.0", |
||||
"pinia": "^2.0.24", |
||||
"pinia-plugin-persist-uni": "^1.2.0", |
||||
"vue": "^3.2.37", |
||||
"vue-i18n": "^9.1.9" |
||||
}, |
||||
"devDependencies": { |
||||
"@dcloudio/types": "^3.0.13", |
||||
"@dcloudio/uni-automator": "3.0.0-alpha-3060420220922001", |
||||
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3060420220922001", |
||||
"@dcloudio/uni-stacktracey": "3.0.0-alpha-3060420220922001", |
||||
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-3060420220922001", |
||||
"sass": "^1.63.6", |
||||
"sass-loader": "^13.3.2", |
||||
"vite": "^2.9.14" |
||||
} |
||||
} |
||||
@ -0,0 +1,19 @@ |
||||
<script> |
||||
export default { |
||||
onLaunch: function () { |
||||
console.log('App Launch') |
||||
}, |
||||
onShow: function () { |
||||
console.log('App Show') |
||||
}, |
||||
onHide: function () { |
||||
console.log('App Hide') |
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss"> |
||||
/*每个页面公共css */ |
||||
@import "uni_modules/uview-plus/index.scss"; |
||||
@import "static/style/index.scss"; |
||||
</style> |
||||
@ -0,0 +1,10 @@ |
||||
import request from '../request/index.js'; |
||||
|
||||
export function getAliCompanyInfo(data) { |
||||
return request({ |
||||
url: 'alipay/findAliCompanyInfo', |
||||
method: 'POST', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
@ -0,0 +1,10 @@ |
||||
import request from '../request/index.js'; |
||||
|
||||
export function login(data) { |
||||
return request({ |
||||
url: 'api-admin/pcLogin', |
||||
method: 'POST', |
||||
data, |
||||
noToken: true |
||||
}); |
||||
} |
||||
@ -0,0 +1,11 @@ |
||||
|
||||
// 请求状态码
|
||||
const RESPONSE_TYPE = { |
||||
SUCCESS: '0000', |
||||
RELOGIN: 'E403', |
||||
ERROR: '4000' |
||||
}; |
||||
|
||||
export default { |
||||
RESPONSE_TYPE, |
||||
}; |
||||
@ -0,0 +1,5 @@ |
||||
import common from './common.js' |
||||
|
||||
export default { |
||||
...common |
||||
} |
||||
@ -0,0 +1,75 @@ |
||||
/** |
||||
* Platform v1.0.0 |
||||
* @Class Platform |
||||
* @description jtools-platform 1.0.0 全平台兼容 |
||||
* @Author lidongtony |
||||
* @Date 2021-04-07 |
||||
* @Email lidongtony@qq.com |
||||
*/ |
||||
|
||||
// #ifdef H5
|
||||
// 微信H5
|
||||
import wxsdk from '@/jtools/wechat/sdk'; |
||||
// #endif
|
||||
export default { |
||||
// 获取当前运行平台
|
||||
get() { |
||||
let platform = ''; |
||||
// #ifdef H5
|
||||
wxsdk.isWechat() ? (platform = 'wxOfficialAccount') : (platform = 'H5'); |
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
platform = 'App'; |
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
platform = 'wxMiniProgram'; |
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
platform = 'alipayMiniProgram'; |
||||
// #endif
|
||||
if (platform !== '') { |
||||
uni.setStorageSync('platform', platform); |
||||
} else { |
||||
uni.showToast({ |
||||
title: '暂不支持该平台', |
||||
icon: 'none' |
||||
}); |
||||
} |
||||
return platform; |
||||
}, |
||||
set(platform) { |
||||
uni.setStorageSync('platform', platform); |
||||
return platform; |
||||
}, |
||||
|
||||
// 检测当前运行机型
|
||||
device() { |
||||
return uni.getSystemInfoSync().platform; |
||||
}, |
||||
|
||||
// 获取前端真实主机
|
||||
host() { |
||||
let host = location.origin; |
||||
let basePath = router.$route.options.base; |
||||
let mode = router.$route.options.mode; |
||||
host += basePath; |
||||
if (mode === 'hash') { |
||||
host += '#/'; |
||||
} |
||||
return host; |
||||
}, |
||||
|
||||
// 处理wechat jssdk 签名网址(针对IOS微信浏览器做优化)
|
||||
entry() { |
||||
let that = this; |
||||
var entryUrl = location.href; |
||||
if (this.device() === 'ios') { |
||||
if (typeof location.entryUrl !== 'undefined') { |
||||
entryUrl = location.entryUrl; |
||||
} else { |
||||
location.entryUrl = entryUrl; |
||||
} |
||||
} |
||||
return entryUrl; |
||||
} |
||||
}; |
||||
@ -0,0 +1,47 @@ |
||||
import storage from '../storage/index.js'; |
||||
//把配置项单独处理
|
||||
let server_url = ' '; // 请求地址
|
||||
let token = ' '; // 凭证
|
||||
|
||||
server_url = import.meta.env.VITE_APP_BASE_API; //环境配置
|
||||
function service(options = {}) { |
||||
storage.get('token') && (token = storage.get('token')); |
||||
options.url = `${server_url}${options.url}`; |
||||
if (!options.noToken) { |
||||
if (!token.trim()) { |
||||
uni.redirectTo({ |
||||
url: '/pages/login/login' |
||||
}); |
||||
} else { |
||||
options.header = { |
||||
Authorization: `Bearer ${token}` |
||||
}; |
||||
} |
||||
} |
||||
return new Promise((resolved, rejected) => { |
||||
//成功
|
||||
options.success = res => { |
||||
if (res.data.code == 'E403') { |
||||
// 未登录
|
||||
uni.showToast({ |
||||
title: res.data.message, |
||||
icon: 'none' |
||||
}); |
||||
uni.redirectTo({ |
||||
url: '/pages/login/login' |
||||
}); |
||||
//请求成功
|
||||
resolved(res.data); |
||||
} else { |
||||
//请求成功
|
||||
resolved(res.data); |
||||
} |
||||
}; |
||||
//错误
|
||||
options.fail = err => { |
||||
rejected(err); //错误
|
||||
}; |
||||
uni.request(options); |
||||
}); |
||||
} |
||||
export default service; |
||||
@ -0,0 +1,40 @@ |
||||
const APP_NAME = import.meta.env.VITE_APP_TITLE |
||||
export default { |
||||
set(key, value) { |
||||
// 命名规则 小程序名称-环境
|
||||
const storageName = `${APP_NAME}-${process.env.NODE_ENV}` |
||||
const temp = uni.getStorageSync(storageName) || {} |
||||
temp[key] = value |
||||
uni.setStorageSync(storageName, temp) |
||||
}, |
||||
get(key) { |
||||
// 命名规则 小程序名称-环境
|
||||
const storageName = `${APP_NAME}-${process.env.NODE_ENV}` |
||||
const temp = uni.getStorageSync(storageName) || {} |
||||
if(temp.hasOwnProperty(key)) { |
||||
return temp[key] |
||||
} else { |
||||
return undefined |
||||
} |
||||
}, |
||||
remove(key) { |
||||
// 命名规则 小程序名称-环境
|
||||
const storageName = `${APP_NAME}-${process.env.NODE_ENV}` |
||||
const temp = uni.getStorageSync(storageName) || {} |
||||
if(temp.hasOwnProperty(key)) { |
||||
delete temp[key]; |
||||
uni.setStorageSync(storageName, temp); |
||||
} |
||||
}, |
||||
has(key) { |
||||
// 命名规则 小程序名称-环境
|
||||
const storageName = `${APP_NAME}-${process.env.NODE_ENV}` |
||||
const temp = uni.getStorageSync(storageName) || {} |
||||
return temp.hasOwnProperty(key) |
||||
}, |
||||
clear() { |
||||
// 命名规则 小程序名称-环境
|
||||
const storageName = `${APP_NAME}-${process.env.NODE_ENV}` |
||||
uni.removeStorageSync(storageName) |
||||
} |
||||
} |
||||
@ -0,0 +1,18 @@ |
||||
import { createPinia } from 'pinia'; |
||||
|
||||
// 自动注入所有pinia模块
|
||||
const files = import.meta.globEager('./*.js'); |
||||
const modules = {}; |
||||
Object.keys(files).forEach((key) => { |
||||
modules[key.replace(/(.*\/)*([^.]+).*/gi, '$2')] = files[key].default; |
||||
}); |
||||
|
||||
export const setupPinia = (app) => { |
||||
const pinia = createPinia(); |
||||
|
||||
app.use(pinia); |
||||
}; |
||||
|
||||
export default (name) => { |
||||
return modules[name](); |
||||
}; |
||||
@ -0,0 +1,68 @@ |
||||
import { defineStore } from 'pinia'; |
||||
import http from '@/jtools/request/index'; |
||||
import constants from '@/jtools/constants'; |
||||
import storage from '@/jtools/storage'; |
||||
|
||||
const user = defineStore({ |
||||
id: 'user', |
||||
state: () => ({ |
||||
token: storage.get('token'), |
||||
isLogin: storage.get('isLogin'), // 是否登陆
|
||||
userInfo: storage.get('userInfo'), // 用户信息
|
||||
}), |
||||
|
||||
actions: { |
||||
// 登录
|
||||
showAuth() { |
||||
// router.replaceAll('/pages/login/login');
|
||||
}, |
||||
|
||||
// 获取个人信息
|
||||
async getInfo() { |
||||
const { error, data } = await userApi.profile(); |
||||
if (error !== 0) return; |
||||
this.userInfo = data; |
||||
return Promise.resolve(data); |
||||
}, |
||||
|
||||
|
||||
// 设置token
|
||||
setToken(token = '') { |
||||
if (token === '') { |
||||
this.isLogin = false; |
||||
this.token = '' |
||||
this.userInfo = {} |
||||
storage.set('token', ''); |
||||
storage.set('isLogin', false); |
||||
storage.set('userInfo', {}); |
||||
} else { |
||||
this.isLogin = true; |
||||
this.token = token |
||||
storage.set('token', token); |
||||
storage.set('isLogin', true); |
||||
} |
||||
return this.isLogin; |
||||
}, |
||||
|
||||
// 重置用户默认数据
|
||||
resetUserData() { |
||||
this.setToken(); |
||||
}, |
||||
// 登出
|
||||
async logout(force = false) { |
||||
if (!force) { |
||||
const { error } = await userApi.logout(); |
||||
if (error === 0) { |
||||
this.resetUserData(); |
||||
} |
||||
} |
||||
if (force) { |
||||
this.resetUserData(); |
||||
} |
||||
|
||||
return !this.isLogin; |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
export default user; |
||||
@ -0,0 +1,31 @@ |
||||
let timer; |
||||
let flag; |
||||
/** |
||||
* 节流原理:在一定时间内,只能触发一次 |
||||
* |
||||
* @param {Function} func 要执行的回调函数 |
||||
* @param {Number} wait 延时的时间 |
||||
* @param {Boolean} immediate 是否立即执行 |
||||
* @return null |
||||
*/ |
||||
function throttle(func, wait = 500, immediate = true) { |
||||
if (immediate) { |
||||
if (!flag) { |
||||
flag = true; |
||||
// 如果是立即执行,则在wait毫秒内开始时执行
|
||||
typeof func === 'function' && func(); |
||||
timer = setTimeout(() => { |
||||
flag = false; |
||||
}, wait); |
||||
} else { |
||||
} |
||||
} else if (!flag) { |
||||
flag = true; |
||||
// 如果是非立即执行,则在wait毫秒内的结束处执行
|
||||
timer = setTimeout(() => { |
||||
flag = false; |
||||
typeof func === 'function' && func(); |
||||
}, wait); |
||||
} |
||||
} |
||||
export default throttle; |
||||
@ -0,0 +1,170 @@ |
||||
var jweixin = require("jweixin-module"); |
||||
import http from "@/jtools/request/index"; |
||||
import $platform from "@/jtools/platform"; |
||||
export default { |
||||
//判断是否在微信中
|
||||
isWechat() { |
||||
var ua = window.navigator.userAgent.toLowerCase(); |
||||
if (ua.match(/micromessenger/i) == "micromessenger") { |
||||
return true; |
||||
} else { |
||||
return false; |
||||
} |
||||
}, |
||||
// 鉴权页面
|
||||
initJssdk(callback) { |
||||
http("common.wxJssdk", { |
||||
uri: encodeURIComponent($platform.entry()) |
||||
}).then(res => { |
||||
jweixin.config({ |
||||
debug: res.data.debug, |
||||
appId: res.data.appId, |
||||
timestamp: res.data.timestamp, |
||||
nonceStr: res.data.nonceStr, |
||||
signature: res.data.signature, |
||||
jsApiList: res.data.jsApiList, |
||||
openTagList: res.data.openTagList |
||||
}); |
||||
if (callback) { |
||||
callback(res.data); |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
//在需要定位页面调用
|
||||
getLocation(callback) { |
||||
this.isWechat() && this.initJssdk(function(res) { |
||||
jweixin.ready(function() { |
||||
jweixin.getLocation({ |
||||
type: "gcj02", // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
|
||||
success: function(res) { |
||||
callback(res); |
||||
}, |
||||
fail: function(res) { |
||||
console.log("%c微信H5sdk,getLocation失败:", |
||||
"color:green;background:yellow"); |
||||
}, |
||||
}); |
||||
}); |
||||
}); |
||||
}, |
||||
|
||||
//获取微信收货地址
|
||||
openAddress(callback) { |
||||
this.isWechat() && this.initJssdk(function(res) { |
||||
jweixin.ready(function() { |
||||
jweixin.openAddress({ |
||||
success: function(res) { |
||||
callback(res); |
||||
}, |
||||
fail: function(err) { |
||||
console.log("%c微信H5sdk,openAddress失败:", |
||||
"color:green;background:yellow"); |
||||
}, |
||||
complete: function(msg) {} |
||||
}); |
||||
}); |
||||
}); |
||||
}, |
||||
|
||||
// 微信扫码
|
||||
scanQRCode(callback) { |
||||
this.isWechat() && this.initJssdk(function(res) { |
||||
jweixin.ready(function() { |
||||
jweixin.scanQRCode({ |
||||
needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
|
||||
scanType: ["qrCode", "barCode"], // 可以指定扫二维码还是一维码,默认二者都有
|
||||
success: function(res) { |
||||
callback(res); |
||||
}, |
||||
fail: function(res) { |
||||
console.log("%c微信H5sdk,scanQRCode失败:", |
||||
"color:green;background:yellow"); |
||||
}, |
||||
}); |
||||
}); |
||||
}); |
||||
}, |
||||
|
||||
// 微信分享
|
||||
share(data, callback) { |
||||
this.isWechat() && this.initJssdk(function(res) { |
||||
jweixin.ready(function() { |
||||
var shareData = { |
||||
title: data.title, |
||||
desc: data.desc, |
||||
link: data.path, |
||||
imgUrl: data.image, |
||||
success: function(res) { |
||||
callback(res); |
||||
// 分享后的一些操作,比如分享统计等等
|
||||
}, |
||||
cancel: function(res) {} |
||||
}; |
||||
|
||||
jweixin.updateAppMessageShareData(shareData); //新版接口
|
||||
//分享到朋友圈接口
|
||||
// jweixin.updateTimelineShareData(shareData);
|
||||
|
||||
|
||||
}); |
||||
}); |
||||
|
||||
}, |
||||
|
||||
|
||||
// 打开坐标位置
|
||||
openLocation(data, callback) { //打开位置
|
||||
this.isWechat() && this.initJssdk(function(res) { |
||||
jweixin.ready(function() { |
||||
jweixin.openLocation({ //根据传入的坐标打开地图
|
||||
latitude: data.latitude, |
||||
longitude: data.longitude |
||||
}); |
||||
}); |
||||
}); |
||||
}, |
||||
// 选择图片
|
||||
chooseImage(callback) { //选择图片
|
||||
this.isWechat() && this.initJssdk(function(res) { |
||||
jweixin.ready(function() { |
||||
jweixin.chooseImage({ |
||||
count: 1, |
||||
sizeType: ["compressed"], |
||||
sourceType: ["album"], |
||||
success: function(rs) { |
||||
callback(rs); |
||||
} |
||||
}); |
||||
}); |
||||
}); |
||||
}, |
||||
|
||||
//微信支付
|
||||
wxpay(data, callback) { |
||||
let that = this; |
||||
this.isWechat() && this.initJssdk(function(res) { |
||||
jweixin.ready(function() { |
||||
jweixin.chooseWXPay({ |
||||
timestamp: data |
||||
.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
|
||||
nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
|
||||
package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
|
||||
signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
|
||||
paySign: data.paySign, // 支付签名
|
||||
success: function(res) { |
||||
callback(res); |
||||
}, |
||||
fail: function(res) { |
||||
console.log("%c微信H5sdk,chooseWXPay失败:", |
||||
"color:green;background:yellow"); |
||||
callback(res); |
||||
}, |
||||
cancel: function(res) { |
||||
|
||||
}, |
||||
}); |
||||
}); |
||||
}); |
||||
} |
||||
}; |
||||
@ -0,0 +1,19 @@ |
||||
import { |
||||
createSSRApp |
||||
} from "vue"; |
||||
import uviewPlus from './uni_modules/uview-plus' |
||||
import platform from '@/jtools/platform'; |
||||
import constants from '@/jtools/constants'; |
||||
import storage from '@/jtools/storage'; |
||||
|
||||
import App from "./App.vue"; |
||||
export function createApp() { |
||||
const app = createSSRApp(App); |
||||
app.config.globalProperties.$platform=platform |
||||
app.config.globalProperties.$constants=constants |
||||
app.config.globalProperties.$storage=storage |
||||
app.use(uviewPlus) |
||||
return { |
||||
app, |
||||
}; |
||||
} |
||||
@ -0,0 +1,77 @@ |
||||
{ |
||||
"name" : "金武联驾校", |
||||
"appid" : "__UNI__401C711", |
||||
"description" : "", |
||||
"versionName" : "1.0.0", |
||||
"versionCode" : "100", |
||||
"transformPx" : false, |
||||
/* 5+App特有相关 */ |
||||
"app-plus" : { |
||||
"usingComponents" : true, |
||||
"nvueStyleCompiler" : "uni-app", |
||||
"compilerVersion" : 3, |
||||
"splashscreen" : { |
||||
"alwaysShowBeforeRender" : true, |
||||
"waiting" : true, |
||||
"autoclose" : true, |
||||
"delay" : 0 |
||||
}, |
||||
/* 模块配置 */ |
||||
"modules" : {}, |
||||
/* 应用发布信息 */ |
||||
"distribute" : { |
||||
/* android打包配置 */ |
||||
"android" : { |
||||
"permissions" : [ |
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", |
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", |
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>", |
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>", |
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", |
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", |
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", |
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>", |
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", |
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", |
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", |
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", |
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", |
||||
"<uses-feature android:name=\"android.hardware.camera\"/>", |
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" |
||||
] |
||||
}, |
||||
/* ios打包配置 */ |
||||
"ios" : {}, |
||||
/* SDK配置 */ |
||||
"sdkConfigs" : {} |
||||
} |
||||
}, |
||||
/* 快应用特有相关 */ |
||||
"quickapp" : {}, |
||||
/* 小程序特有相关 */ |
||||
"mp-weixin" : { |
||||
"appid" : "wx756a7425037609fb", |
||||
"setting" : { |
||||
"urlCheck" : false, |
||||
"minified" : true, |
||||
"es6" : false |
||||
}, |
||||
"usingComponents" : true, |
||||
"permission" : {} |
||||
}, |
||||
"mp-alipay" : { |
||||
"usingComponents" : true |
||||
}, |
||||
"mp-baidu" : { |
||||
"usingComponents" : true |
||||
}, |
||||
"mp-toutiao" : { |
||||
"usingComponents" : true |
||||
}, |
||||
"uniStatistics" : { |
||||
"enable" : false |
||||
}, |
||||
"vueVersion" : "3", |
||||
"fallbackLocale" : "zh-Hans", |
||||
"locale" : "zh-Hans" |
||||
} |
||||
@ -0,0 +1,62 @@ |
||||
{ |
||||
"easycom": { |
||||
"^u-(.*)": "uview-plus/components/u-$1/u-$1.vue", |
||||
"^j-(.*)": "@/components/j-$1/j-$1.vue" |
||||
}, |
||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages |
||||
{ |
||||
"path": "pages/index/index", |
||||
"style": { |
||||
"navigationBarTitleText": "金武联驾考" |
||||
} |
||||
}, |
||||
{ |
||||
"path": "pages/me/index", |
||||
"style": { |
||||
"navigationBarTitleText": "我的" |
||||
} |
||||
} |
||||
,{ |
||||
"path" : "pages/questionBank/questionBank", |
||||
"style" : |
||||
{ |
||||
"navigationStyle": "custom", |
||||
"enablePullDownRefresh": false |
||||
} |
||||
|
||||
}, |
||||
{ |
||||
"path": "pages/questionBank/practiceExams", |
||||
"style": { |
||||
"navigationBarTitleText": "模拟考试" |
||||
} |
||||
} |
||||
], |
||||
|
||||
"globalStyle": { |
||||
"navigationBarTextStyle": "black", |
||||
"navigationBarTitleText": "通用模板", |
||||
"navigationBarBackgroundColor": "#FFF", |
||||
"backgroundColor": "#FFF" |
||||
}, |
||||
"tabBar": { |
||||
"borderStyle": "white", |
||||
"selectedColor": "#333333", |
||||
"backgroundColor": "#FFFFFF", |
||||
"color": "#999999", |
||||
"list": [{ |
||||
"pagePath": "pages/index/index", |
||||
"iconPath": "static/image/tabbar/tab-home.png", |
||||
"selectedIconPath":"static/image/tabbar/tab-home-selected.png", |
||||
"text": "首页" |
||||
}, |
||||
|
||||
{ |
||||
"pagePath": "pages/me/index", |
||||
"iconPath": "static/image/tabbar/tab-mine.png", |
||||
"selectedIconPath": "static/image/tabbar/tab-mine-selected.png", |
||||
"text": "我的" |
||||
} |
||||
] |
||||
} |
||||
} |
||||
@ -0,0 +1,175 @@ |
||||
<template> |
||||
<view> |
||||
<view class="box-nav"> |
||||
<image style="width: 100%;" src="../../static/image/index/index_bg.png"></image> |
||||
<view class="center-box flex jc-sb ai-c"> |
||||
<view class="box-item flex ai-c jc-c"> |
||||
<view class="btn-item flex ai-c jc-c"> |
||||
<view class="text-center cor-fff" style="line-height: 40rpx;" @tap="toAnswer"> |
||||
<view class="fs16">顺序练习</view> |
||||
<text class="fs14">0/2344</text> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
<view class="box-item flex ai-c jc-c"> |
||||
<view class="btn2-item flex ai-c jc-c"> |
||||
<view class="text-center cor-fff" style="line-height: 40rpx;"> |
||||
<view class="fs16">模拟考试</view> |
||||
<text class="fs14">去考试</text> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
<view style="padding: 0 28rpx;margin-top: 110rpx;"> |
||||
<view class="tabs-box"> |
||||
<view class="wp33 flex ai-c jc-c"> |
||||
<view class="text-center wp100"> |
||||
<image style="width: 72rpx;height: 72rpx;margin: 0 auto" |
||||
src="../../static/image/index/vipicon.png"> |
||||
</image> |
||||
<view class="mt5">VIP课程</view> |
||||
</view> |
||||
</view> |
||||
<view class="wp33 flex ai-c jc-c"> |
||||
<view class="text-center wp100"> |
||||
<image style="width: 72rpx;height: 72rpx;margin: 0 auto" |
||||
src="../../static/image/index/500icon.png"> |
||||
</image> |
||||
<view class="mt5">精简500题</view> |
||||
</view> |
||||
</view> |
||||
<view class="wp33 flex ai-c jc-c"> |
||||
<view class="text-center wp100"> |
||||
<image style="width: 72rpx;height: 72rpx;margin: 0 auto" |
||||
src="../../static/image/index/zxicon.png"> |
||||
</image> |
||||
<view class="mt5">专项练习</view> |
||||
</view> |
||||
</view> |
||||
<view class="wp33 flex ai-c jc-c"> |
||||
<view class="text-center wp100"> |
||||
<image style="width: 72rpx;height: 72rpx;margin: 0 auto;" |
||||
src="../../static/image/index/realicon.png"></image> |
||||
<view class="mt5">真实考场模拟</view> |
||||
</view> |
||||
</view> |
||||
<view class="wp33 flex ai-c jc-c"> |
||||
<view class="text-center wp100"> |
||||
<image style="width: 72rpx;height: 72rpx;margin: 0 auto" |
||||
src="../../static/image/index/testbeforeicon.png"></image> |
||||
<view class="mt5">考前密卷</view> |
||||
</view> |
||||
</view> |
||||
<view class="wp33 flex ai-c jc-c"> |
||||
<view class="text-center wp100"> |
||||
<image style="width: 72rpx;height: 72rpx;margin: 0 auto" |
||||
src="../../static/image/index/worryicon.png"></image> |
||||
<view class="mt5">错题收藏</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
<view style="padding: 0 28rpx;margin-top: 30rpx;"> |
||||
<view class="video-box"> |
||||
<view class="flex jc-sb ai-c wp100"> |
||||
<text style="color: #05C341;font-size: 36rpx;">科一精品视频课</text> |
||||
<text class="cor-666 fs12">全部10节课 ></text> |
||||
</view> |
||||
<view class="flex ai-c mt20"> |
||||
<image class="contain-box" src="../../static/image/index/index_bg.png"></image> |
||||
<view class="ml15 text-center"> |
||||
<u-button :customStyle="{width:'200rpx',height:'66rpx',borderRadius: '33rpx'}" iconColor="#fff" |
||||
text="去看视频" color="linear-gradient(90deg, #11DF20 0%, #00B74F 100%)" icon="play-circle"> |
||||
</u-button> |
||||
<view class="cor-333 fs15 fw600 mt10">科一易错试题</view> |
||||
</view> |
||||
</view> |
||||
|
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
data() { |
||||
return {} |
||||
}, |
||||
methods: { |
||||
toAnswer() { |
||||
uni.navigateTo({ |
||||
url:"/pages/questionBank/questionBank" |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.box-nav { |
||||
width: 100%; |
||||
position: relative; |
||||
} |
||||
|
||||
.center-box { |
||||
position: absolute; |
||||
width: 100%; |
||||
top: 170rpx; |
||||
padding: 0 28rpx; |
||||
} |
||||
|
||||
.box-item { |
||||
width: 336rpx; |
||||
height: 336rpx; |
||||
background: #FFFFFF; |
||||
border-radius: 16rpx; |
||||
z-index: 999; |
||||
} |
||||
|
||||
.btn-item { |
||||
width: 193rpx; |
||||
height: 193rpx; |
||||
background: linear-gradient(0deg, #11DF20 0%, #00B74F 100%); |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
.btn2-item { |
||||
width: 193rpx; |
||||
height: 193rpx; |
||||
background: linear-gradient(0deg, #E95A0E 0%, #FF9600 100%); |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
::v-deep .u-tabs__wrapper__nav__line { |
||||
background: linear-gradient(90deg, #11DF20 0%, #00B74F 100%) !important; |
||||
bottom: 14rpx !important; |
||||
} |
||||
|
||||
.tabs-box { |
||||
width: 694rpx; |
||||
height: 366rpx; |
||||
background: #FFFFFF; |
||||
border-radius: 16rpx; |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: flex-start; |
||||
flex-wrap: wrap; |
||||
} |
||||
|
||||
.video-box { |
||||
padding: 20rpx; |
||||
width: 694rpx; |
||||
height: 369rpx; |
||||
background: #DEEFE5; |
||||
border: 2px solid #47DB87; |
||||
border-radius: 16rpx; |
||||
} |
||||
|
||||
.contain-box { |
||||
width: 406rpx; |
||||
height: 228rpx; |
||||
background: #00B74F; |
||||
border-radius: 16rpx; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,46 @@ |
||||
<template> |
||||
<view> |
||||
<view class="box-nav"> |
||||
<image style="width: 100%;" src="../../static/image/index/index_bg.png"></image> |
||||
<view class="center-box flex jc-sb ai-c"> |
||||
<view class="box-item flex ai-c jc-c"> |
||||
<view class="btn-item flex ai-c jc-c"> |
||||
<view class="text-center cor-fff" style="line-height: 40rpx;"> |
||||
<view class="fs16">顺序练习</view> |
||||
<text class="fs14">0/2344</text> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
<view class="box-item flex ai-c jc-c"> |
||||
<view class="btn2-item flex ai-c jc-c"> |
||||
<view class="text-center cor-fff" style="line-height: 40rpx;"> |
||||
<view class="fs16">模拟考试</view> |
||||
<text class="fs14">去考试</text> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
|
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "Subject2" |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.box-nav{ |
||||
width: 100%; |
||||
position: relative; |
||||
} |
||||
.center-box{ |
||||
position: absolute; |
||||
width: 100%; |
||||
top: 170rpx; |
||||
padding: 0 28rpx; |
||||
} |
||||
|
||||
</style> |
||||
@ -0,0 +1,52 @@ |
||||
<template> |
||||
<view> |
||||
<u-sticky bgColor="#fff"> |
||||
<u-tabs :list="categoryList" :scrollable="false"></u-tabs> |
||||
</u-sticky> |
||||
<view style="height: 100vh;background-color: rgb(245, 245, 245);"> |
||||
<Subject1 /> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
<script> |
||||
import storage from '@/jtools/storage'; |
||||
import Subject1 from "./components/Subject1"; |
||||
export default { |
||||
components: {Subject1}, |
||||
data() { |
||||
return { |
||||
searchValue:'', |
||||
cityName:'', |
||||
categoryList:[{ |
||||
name:'科目1' |
||||
},{ |
||||
name:'科目2' |
||||
},{ |
||||
name:'科目3' |
||||
},{ |
||||
name:'科目4' |
||||
}] |
||||
}; |
||||
}, |
||||
onLoad() { |
||||
|
||||
}, |
||||
methods:{ |
||||
//切换科目 |
||||
changeCategory(){ |
||||
|
||||
}, |
||||
//获取全部题库 |
||||
getAllQuestion(){ |
||||
|
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
::v-deep .u-tabs__wrapper__nav__line { |
||||
background: linear-gradient(90deg, #11DF20 0%, #00B74F 100%) !important; |
||||
bottom: 14rpx !important; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,27 @@ |
||||
<template> |
||||
<view> |
||||
<web-view :webview-styles="webviewStyles" :src="articleurl"></web-view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
data() { |
||||
return { |
||||
articleurl: '', |
||||
webviewStyles: { |
||||
progress: { |
||||
color: '#FF7200' |
||||
} |
||||
} |
||||
}; |
||||
}, |
||||
onLoad(options) { |
||||
this.articleurl = 'http://www.baidu.com/'; |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
|
||||
</style> |
||||
@ -0,0 +1,255 @@ |
||||
<template> |
||||
<view class="content"> |
||||
<view class="list"> |
||||
<view class="tishi">若您忘记了密码,可在此重新设置新密码。</view> |
||||
<view class="list-call"> |
||||
<u-icon name="phone" color="#EE2626" size="40"></u-icon> |
||||
<input class="sl-input" type="number" v-model="phone" maxlength="11" placeholder="请输入手机号" /> |
||||
</view> |
||||
<view class="list-call"> |
||||
<u-icon name="lock" color="#EE2626" size="40"></u-icon> |
||||
<input class="sl-input" type="text" v-model="password" maxlength="32" placeholder="请输入新密码" |
||||
:password="!showPassword" /> |
||||
<u-icon @click="display" :name="showPassword?'eye-off':'eye-fill'" color="#EE2626" size="40"></u-icon> |
||||
|
||||
</view> |
||||
<view class="list-call"> |
||||
<u-icon name="checkmark-circle" color="#EE2626" size="40"></u-icon> |
||||
<input class="sl-input" type="number" v-model="code" maxlength="4" placeholder="验证码" /> |
||||
<view class="yzm" :class="{ yzms: second>0 }" @tap="getcode">{{yanzhengma}}</view> |
||||
</view> |
||||
</view> |
||||
<view class="button-login" hover-class="button-hover" @tap="bindLogin()"> |
||||
<text>修改密码</text> |
||||
</view> |
||||
|
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
var _this, js; |
||||
export default { |
||||
data() { |
||||
return { |
||||
phone: '', |
||||
second: 0, |
||||
code: "", |
||||
showPassword: false, |
||||
password: '' |
||||
} |
||||
}, |
||||
onLoad() { |
||||
_this = this; |
||||
}, |
||||
computed: { |
||||
yanzhengma() { |
||||
if (this.second == 0) { |
||||
return '获取验证码'; |
||||
} else { |
||||
if (this.second < 10) { |
||||
return '重新获取0' + this.second; |
||||
} else { |
||||
return '重新获取' + this.second; |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
onUnload() { |
||||
this.clear() |
||||
}, |
||||
methods: { |
||||
display() { |
||||
this.showPassword = !this.showPassword |
||||
}, |
||||
clear() { |
||||
clearInterval(js) |
||||
js = null |
||||
this.second = 0 |
||||
}, |
||||
getcode() { |
||||
if (this.phone.length != 11) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: '手机号不正确' |
||||
}); |
||||
return; |
||||
} |
||||
if (this.second > 0) { |
||||
return; |
||||
} |
||||
_this.second = 60; |
||||
js = setInterval(function() { |
||||
_this.second--; |
||||
if (_this.second == 0) { |
||||
_this.clear() |
||||
} |
||||
}, 1000) |
||||
uni.request({ |
||||
url: 'http://***/getPassWord', //仅为示例,并非真实接口地址。 |
||||
data: { |
||||
phone: this.phone, |
||||
type: 'forget' |
||||
}, |
||||
method: 'POST', |
||||
dataType: 'json', |
||||
success: (res) => { |
||||
if (res.data.code != 200) { |
||||
uni.showToast({ |
||||
title: res.data.msg, |
||||
icon: 'none' |
||||
}); |
||||
_this.second = 0; |
||||
} else { |
||||
uni.showToast({ |
||||
title: res.data.msg |
||||
}); |
||||
_this.second = 60; |
||||
js = setInterval(function() { |
||||
_this.second--; |
||||
if (_this.second == 0) { |
||||
_this.clear() |
||||
} |
||||
}, 1000) |
||||
} |
||||
}, |
||||
fail() { |
||||
this.clear() |
||||
} |
||||
}); |
||||
|
||||
|
||||
|
||||
|
||||
}, |
||||
bindLogin() { |
||||
if (this.phone.length != 11) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: '手机号不正确' |
||||
}); |
||||
return; |
||||
} |
||||
if (this.password.length < 6) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: '密码不正确' |
||||
}); |
||||
return; |
||||
} |
||||
if (this.code.length != 4) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: '验证码不正确' |
||||
}); |
||||
return; |
||||
} |
||||
uni.request({ |
||||
url: 'http://***/forget', |
||||
data: { |
||||
phone: this.phone, |
||||
password: this.password, |
||||
code: this.code |
||||
}, |
||||
method: 'POST', |
||||
dataType: 'json', |
||||
success: (res) => { |
||||
if (res.data.code != 200) { |
||||
uni.showToast({ |
||||
title: res.data.msg, |
||||
icon: 'none' |
||||
}); |
||||
} else { |
||||
uni.showToast({ |
||||
title: res.data.msg |
||||
}); |
||||
setTimeout(function() { |
||||
uni.navigateBack(); |
||||
}, 1500) |
||||
} |
||||
} |
||||
}); |
||||
|
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
page { |
||||
background-color: #fff; |
||||
} |
||||
|
||||
.content { |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
} |
||||
|
||||
.tishi { |
||||
color: #999999; |
||||
font-size: 28rpx; |
||||
line-height: 50rpx; |
||||
margin-bottom: 50rpx; |
||||
} |
||||
|
||||
.list { |
||||
display: flex; |
||||
flex-direction: column; |
||||
padding-top: 50rpx; |
||||
padding-left: 70rpx; |
||||
padding-right: 70rpx; |
||||
} |
||||
|
||||
.list-call { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
height: 100rpx; |
||||
color: #333333; |
||||
border-bottom: 0.5px solid #e2e2e2; |
||||
} |
||||
|
||||
|
||||
.list-call .sl-input { |
||||
flex: 1; |
||||
text-align: left; |
||||
font-size: 32rpx; |
||||
margin-left: 16rpx; |
||||
} |
||||
|
||||
.button-login { |
||||
color: #FFFFFF; |
||||
font-size: 34rpx; |
||||
width: 470rpx; |
||||
height: 100rpx; |
||||
background: linear-gradient(-90deg, rgba(193, 25, 32, 1), rgba(238, 38, 38, 1)); |
||||
border-radius: 50rpx; |
||||
line-height: 100rpx; |
||||
text-align: center; |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
margin-top: 100rpx; |
||||
} |
||||
|
||||
.button-hover { |
||||
background: linear-gradient(-90deg, rgba(193, 25, 32, 0.8), rgba(238, 38, 38, 0.8)); |
||||
} |
||||
|
||||
.yzm { |
||||
color: #FF7D13; |
||||
font-size: 24rpx; |
||||
line-height: 64rpx; |
||||
padding-left: 10rpx; |
||||
padding-right: 10rpx; |
||||
width: auto; |
||||
height: 64rpx; |
||||
border: 1rpx solid rgba(255, 131, 30, 1); |
||||
border-radius: 50rpx; |
||||
} |
||||
|
||||
.yzms { |
||||
color: #999999 !important; |
||||
border: 1rpx solid #999999; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,140 @@ |
||||
<template> |
||||
<view class="content"> |
||||
<view class="header"> |
||||
<image src="/static/images/login/logo.jpg"></image> |
||||
</view> |
||||
|
||||
<view class="list"> |
||||
<view class="list-call"> |
||||
<u-icon name="phone" color="#EE2626" size="40"></u-icon> |
||||
<input class="sl-input" v-model="login.phone" type="number" maxlength="11" placeholder="输入手机号" /> |
||||
</view> |
||||
<view class="list-call"> |
||||
<u-icon name="lock" color="#EE2626" size="40"></u-icon> |
||||
<input class="sl-input" v-model="login.password" type="text" maxlength="32" placeholder="输入密码" |
||||
password="true" /> |
||||
</view> |
||||
|
||||
</view> |
||||
|
||||
<view class="button-login" hover-class="button-hover" @tap="bindLogin()"> |
||||
<text>登录</text> |
||||
</view> |
||||
|
||||
<view class="agreenment"> |
||||
<navigator url="/pages/login/forget" open-type="navigate">忘记密码</navigator> |
||||
<text>|</text> |
||||
<navigator url="/pages/login/reg" open-type="navigate">注册账户</navigator> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
data() { |
||||
return { |
||||
login: { |
||||
phone: '', |
||||
password: '' |
||||
} |
||||
|
||||
}; |
||||
}, |
||||
methods: { |
||||
bindLogin() { |
||||
|
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
page { |
||||
background-color: #fff; |
||||
} |
||||
|
||||
.content { |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
} |
||||
|
||||
.header { |
||||
width: 161rpx; |
||||
height: 161rpx; |
||||
border-radius: 50%; |
||||
margin-top: 30rpx; |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
} |
||||
|
||||
.header image { |
||||
width: 161rpx; |
||||
height: 161rpx; |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
.list { |
||||
display: flex; |
||||
flex-direction: column; |
||||
padding-top: 50rpx; |
||||
padding-left: 70rpx; |
||||
padding-right: 70rpx; |
||||
} |
||||
|
||||
.list-call { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
height: 100rpx; |
||||
color: #333333; |
||||
border-bottom: 0.5px solid #e2e2e2; |
||||
} |
||||
|
||||
|
||||
|
||||
.list-call .sl-input { |
||||
flex: 1; |
||||
text-align: left; |
||||
font-size: 32rpx; |
||||
margin-left: 16rpx; |
||||
} |
||||
|
||||
.button-login { |
||||
color: #FFFFFF; |
||||
font-size: 34rpx; |
||||
width: 470rpx; |
||||
height: 100rpx; |
||||
background: linear-gradient(-90deg, rgba(193, 25, 32, 1), rgba(238, 38, 38, 1)); |
||||
border-radius: 50rpx; |
||||
line-height: 100rpx; |
||||
text-align: center; |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
margin-top: 100rpx; |
||||
} |
||||
|
||||
.button-hover { |
||||
background: linear-gradient(-90deg, rgba(193, 25, 32, 0.8), rgba(238, 38, 38, 0.8)); |
||||
} |
||||
|
||||
.agreenment { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: center; |
||||
align-items: center; |
||||
font-size: 30rpx; |
||||
margin-top: 80rpx; |
||||
color: #FFA800; |
||||
text-align: center; |
||||
height: 40rpx; |
||||
line-height: 40rpx; |
||||
} |
||||
|
||||
.agreenment text { |
||||
font-size: 24rpx; |
||||
margin-left: 15rpx; |
||||
margin-right: 15rpx; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,303 @@ |
||||
<template> |
||||
<view class="content"> |
||||
<view class="header"> |
||||
<image src="/static/images/login/logo.jpg"></image> |
||||
</view> |
||||
|
||||
<view class="list"> |
||||
<view class="list-call"> |
||||
<u-icon name="phone" color="#EE2626" size="40"></u-icon> |
||||
<input class="sl-input" v-model="phone" type="number" maxlength="11" placeholder="手机号" /> |
||||
</view> |
||||
<view class="list-call"> |
||||
<u-icon name="lock" color="#EE2626" size="40"></u-icon> |
||||
<input class="sl-input" v-model="password" type="text" maxlength="32" placeholder="登录密码" |
||||
:password="!showPassword" /> |
||||
<u-icon @click="display" :name="showPassword?'eye-off':'eye-fill'" color="#EE2626" size="40"></u-icon> |
||||
</view> |
||||
<view class="list-call"> |
||||
<u-icon name="checkmark-circle" color="#EE2626" size="40"></u-icon> |
||||
<input class="sl-input" v-model="code" type="number" maxlength="4" placeholder="验证码" /> |
||||
<view class="yzm" :class="{ yzms: second>0 }" @tap="getcode">{{yanzhengma}}</view> |
||||
</view> |
||||
|
||||
</view> |
||||
|
||||
<view class="button-login" hover-class="button-hover" @tap="bindLogin"> |
||||
<text>注册</text> |
||||
</view> |
||||
|
||||
<view class="agreement"> |
||||
<image @tap="agreementSuccess" |
||||
:src="agreement==true?'/static/images/login/ty1.png':'/static/images/login/ty0.png'"></image> |
||||
<text @tap="agreementSuccess"> 同意</text> |
||||
<navigator url="/pages/login/agreement?id=1" open-type="navigate">《软件用户协议》</navigator> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
var _this, js; |
||||
export default { |
||||
onLoad() { |
||||
_this = this; |
||||
|
||||
}, |
||||
onUnload() { |
||||
clearInterval(js) |
||||
this.second = 0; |
||||
}, |
||||
data() { |
||||
return { |
||||
phone: '', |
||||
password: '', |
||||
code: '', |
||||
agreement: true, |
||||
showPassword: false, |
||||
second: 0 |
||||
}; |
||||
}, |
||||
computed: { |
||||
yanzhengma() { |
||||
if (this.second == 0) { |
||||
return '获取验证码'; |
||||
} else { |
||||
if (this.second < 10) { |
||||
return '重新获取0' + this.second; |
||||
} else { |
||||
return '重新获取' + this.second; |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
onUnload() { |
||||
this.clear() |
||||
}, |
||||
methods: { |
||||
clear() { |
||||
clearInterval(js) |
||||
js = null |
||||
this.second = 0 |
||||
}, |
||||
display() { |
||||
this.showPassword = !this.showPassword |
||||
}, |
||||
agreementSuccess() { |
||||
this.agreement = !this.agreement; |
||||
}, |
||||
getcode() { |
||||
if (this.phone.length != 11) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: '手机号不正确' |
||||
}); |
||||
return; |
||||
} |
||||
if (this.second > 0) { |
||||
return; |
||||
} |
||||
this.second = 60; |
||||
//请求业务 |
||||
js = setInterval(function() { |
||||
_this.second--; |
||||
if (_this.second == 0) { |
||||
_this.clear() |
||||
} |
||||
}, 1000) |
||||
// uni.request({ |
||||
// url: 'http://***/getcode.html', //仅为示例,并非真实接口地址。 |
||||
// data: { |
||||
// phone: this.phone, |
||||
// type: 'reg' |
||||
// }, |
||||
// method: 'POST', |
||||
// dataType: 'json', |
||||
// success: (res) => { |
||||
// if (res.data.code != 200) { |
||||
// uni.showToast({ |
||||
// title: res.data.msg, |
||||
// icon: 'none' |
||||
// }); |
||||
// } else { |
||||
// uni.showToast({ |
||||
// title: res.data.msg |
||||
// }); |
||||
// js = setInterval(function() { |
||||
// _this.second--; |
||||
// if (_this.second == 0) { |
||||
// _this.clear() |
||||
// } |
||||
// }, 1000) |
||||
// } |
||||
// }, |
||||
// fail() { |
||||
// this.second == 0 |
||||
// } |
||||
// }); |
||||
}, |
||||
bindLogin() { |
||||
if (this.agreement == false) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: '请先阅读《软件用户协议》' |
||||
}); |
||||
return; |
||||
} |
||||
if (this.phone.length != 11) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: '手机号不正确' |
||||
}); |
||||
return; |
||||
} |
||||
if (this.password.length < 6) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: '密码不正确' |
||||
}); |
||||
return; |
||||
} |
||||
if (this.code.length != 4) { |
||||
uni.showToast({ |
||||
icon: 'none', |
||||
title: '验证码不正确' |
||||
}); |
||||
return; |
||||
} |
||||
uni.request({ |
||||
url: 'http://***/reg.html', |
||||
data: { |
||||
phone: this.phone, |
||||
password: this.password, |
||||
code: this.code, |
||||
}, |
||||
method: 'POST', |
||||
dataType: 'json', |
||||
success: (res) => { |
||||
if (res.data.code != 200) { |
||||
uni.showToast({ |
||||
title: res.data.msg, |
||||
icon: 'none' |
||||
}); |
||||
} else { |
||||
uni.showToast({ |
||||
title: res.data.msg |
||||
}); |
||||
setTimeout(function() { |
||||
uni.navigateBack(); |
||||
}, 1500) |
||||
} |
||||
} |
||||
}); |
||||
|
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
page { |
||||
background-color: #fff; |
||||
} |
||||
|
||||
.content { |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
} |
||||
|
||||
.header { |
||||
width: 161rpx; |
||||
height: 161rpx; |
||||
border-radius: 50%; |
||||
margin-top: 30rpx; |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
} |
||||
|
||||
.header image { |
||||
width: 161rpx; |
||||
height: 161rpx; |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
.list { |
||||
display: flex; |
||||
flex-direction: column; |
||||
padding-top: 50rpx; |
||||
padding-left: 70rpx; |
||||
padding-right: 70rpx; |
||||
} |
||||
|
||||
.list-call { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
height: 100rpx; |
||||
color: #333333; |
||||
border-bottom: 0.5px solid #e2e2e2; |
||||
} |
||||
|
||||
|
||||
|
||||
.list-call .sl-input { |
||||
flex: 1; |
||||
text-align: left; |
||||
font-size: 32rpx; |
||||
margin-left: 16rpx; |
||||
} |
||||
|
||||
.yzm { |
||||
color: #FF7D13; |
||||
font-size: 24rpx; |
||||
line-height: 64rpx; |
||||
padding-left: 10rpx; |
||||
padding-right: 10rpx; |
||||
width: auto; |
||||
height: 64rpx; |
||||
border: 1rpx solid #FFA800; |
||||
border-radius: 50rpx; |
||||
} |
||||
|
||||
.yzms { |
||||
color: #999999 !important; |
||||
border: 1rpx solid #999999; |
||||
} |
||||
|
||||
.button-login { |
||||
color: #FFFFFF; |
||||
font-size: 34rpx; |
||||
width: 470rpx; |
||||
height: 100rpx; |
||||
line-height: 100rpx; |
||||
background: linear-gradient(-90deg, rgba(193, 25, 32, 1), rgba(238, 38, 38, 1)); |
||||
border-radius: 50rpx; |
||||
text-align: center; |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
margin-top: 100rpx; |
||||
} |
||||
|
||||
.button-hover { |
||||
background: linear-gradient(-90deg, rgba(193, 25, 32, 0.8), rgba(238, 38, 38, 0.8)); |
||||
} |
||||
|
||||
.agreement { |
||||
display: flex; |
||||
flex-direction: row; |
||||
justify-content: center; |
||||
align-items: center; |
||||
font-size: 30rpx; |
||||
margin-top: 80rpx; |
||||
color: #FFA800; |
||||
text-align: center; |
||||
height: 40rpx; |
||||
line-height: 40rpx; |
||||
} |
||||
|
||||
.agreement image { |
||||
width: 40rpx; |
||||
height: 40rpx; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,30 @@ |
||||
<template> |
||||
<view> |
||||
<view class="bg-white padding"> |
||||
<view class="text-xl text-center">我的 |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
<script> |
||||
export default { |
||||
components: {}, |
||||
data() { |
||||
return { |
||||
|
||||
}; |
||||
}, |
||||
onShow() { |
||||
//加载 |
||||
}, |
||||
onLoad() { |
||||
|
||||
}, |
||||
methods: { |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
|
||||
</style> |
||||
@ -0,0 +1,13 @@ |
||||
<template> |
||||
|
||||
|
||||
<u-empty mode="page"> |
||||
<u-button slot="bottom" size="medium" @click="$Router.pushTab('/pages/tabbar/home')"> |
||||
去首页 |
||||
</u-button> |
||||
</u-empty> |
||||
</template> |
||||
|
||||
<script></script> |
||||
|
||||
<style></style> |
||||
@ -0,0 +1,146 @@ |
||||
<template> |
||||
<view style="margin-top: 100px;"> |
||||
<view class="flex ai-c jc-c"> |
||||
<view class="flex type_box"> |
||||
<view class="type_item" :class="tCurrent==0?'checked':'unchecked'" @tap="sectionChange(0)">答题</view> |
||||
<view class="type_item" :class="tCurrent==1?'checked':'unchecked'" @tap="sectionChange(1)">背题</view> |
||||
</view> |
||||
</view> |
||||
<view class="m14lr"> |
||||
<text class="tag_box">{{questionList[currentIndex].questionTypeDesc}}</text> |
||||
<text class="fs18">{{questionList[currentIndex].questionDesc}}</text> |
||||
</view> |
||||
<view class="flex m14lr ai-c mt20" v-for="(item,index) in questionList[currentIndex].optionList" :key="item.op"> |
||||
<template v-if="clickAnswer&&item.op===questionList[currentIndex].rightAnswer"> |
||||
<u-icon name="checkmark-circle-fill" color="#05C341" size="30"></u-icon> |
||||
</template> |
||||
<template v-else-if="clickAnswer===item.op&&item.op!==questionList[currentIndex].rightAnswer"> |
||||
<u-icon name="close-circle-fill" color="red" size="30"></u-icon> |
||||
</template> |
||||
<template v-else-if="!clickAnswer"> |
||||
<view class="option_item">{{item.op}}</view> |
||||
</template> |
||||
<text class="fs18">{{item.opDesc}}</text> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
data() { |
||||
return { |
||||
tCurrent:0, |
||||
clickAnswer:undefined, |
||||
questionList:[], |
||||
currentIndex:0, |
||||
} |
||||
}, |
||||
onshow(){ |
||||
this.getQuestionList() |
||||
}, |
||||
methods: { |
||||
sectionChange(index){ |
||||
this.tCurrent=index |
||||
}, |
||||
getQuestionList(){ |
||||
this.questionList=[{ |
||||
questionId:0, |
||||
questionTypeDesc:'单选', |
||||
questionDesc:'在实习期内驾驶机动车的,应当在车身后部粘贴或者悬挂哪种标志?', |
||||
optionList:[{ |
||||
op:'A', |
||||
opDesc:'注意新手标志' |
||||
},{ |
||||
op:'B', |
||||
opDesc:'注意新手标志' |
||||
},{ |
||||
op:'C', |
||||
opDesc:'注意新手标志' |
||||
},{ |
||||
op:'D', |
||||
opDesc:'注意新手标志' |
||||
}], |
||||
rightOp:'C', |
||||
},{ |
||||
questionId:1, |
||||
questionDesc:'在实习期内驾驶机动车的,应当在车身后部粘贴或者悬挂哪种标志?', |
||||
optionList:[{ |
||||
op:'A', |
||||
opDesc:'注意新手标志' |
||||
},{ |
||||
op:'B', |
||||
opDesc:'注意新手标志' |
||||
},{ |
||||
op:'C', |
||||
opDesc:'注意新手标志' |
||||
},{ |
||||
op:'D', |
||||
opDesc:'注意新手标志' |
||||
}], |
||||
rightOp:'B', |
||||
},{ |
||||
questionId:2, |
||||
questionDesc:'在实习期内驾驶机动车的,应当在车身后部粘贴或者悬挂哪种标志?', |
||||
optionList:[{ |
||||
op:'A', |
||||
opDesc:'注意新手标志' |
||||
},{ |
||||
op:'B', |
||||
opDesc:'注意新手标志' |
||||
},{ |
||||
op:'C', |
||||
opDesc:'注意新手标志' |
||||
},{ |
||||
op:'D', |
||||
opDesc:'注意新手标志' |
||||
}], |
||||
rightOp:'A', |
||||
}] |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.type_box{ |
||||
width: 280rpx; |
||||
height: 72rpx; |
||||
background-color: #FFFFFF; |
||||
border-radius: 8rpx; |
||||
} |
||||
.type_item{ |
||||
text-align: center; |
||||
line-height: 64rpx; |
||||
width: 126rpx; |
||||
height: 64rpx; |
||||
border-radius: 8rpx; |
||||
} |
||||
.checked{ |
||||
color: #fff; |
||||
background-color: #05C341; |
||||
} |
||||
.unchecked{ |
||||
color: #666; |
||||
} |
||||
.tag_box{ |
||||
display: inline-block; |
||||
width: 78rpx; |
||||
height: 42rpx; |
||||
background: #05C341; |
||||
border-radius: 8rpx; |
||||
text-align: center; |
||||
line-height: 42rpx; |
||||
color: #fff; |
||||
font-size: 12px; |
||||
margin-right: 5px; |
||||
} |
||||
.option_item{ |
||||
width: 60rpx; |
||||
height: 60rpx; |
||||
border: 1px solid #666; |
||||
border-radius: 50%; |
||||
text-align: center; |
||||
line-height: 60rpx; |
||||
margin-right: 30rpx; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,22 @@ |
||||
<template> |
||||
<view> |
||||
|
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
data() { |
||||
return { |
||||
|
||||
} |
||||
}, |
||||
methods: { |
||||
|
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
|
||||
</style> |
||||
@ -0,0 +1,28 @@ |
||||
<template> |
||||
<view> |
||||
<u-navbar title="顺序答题" @rightClick="rightClick" :autoBack="true"> |
||||
</u-navbar> |
||||
<Question /> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import Question from './components/Question.vue'; |
||||
export default { |
||||
components: {Question}, |
||||
data() { |
||||
return { |
||||
|
||||
} |
||||
}, |
||||
methods: { |
||||
rightClick(){ |
||||
console.log('返回'); |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
|
||||
</style> |
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 332 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,3 @@ |
||||
page { |
||||
color: $uni-color-primary; |
||||
} |
||||
@ -0,0 +1,429 @@ |
||||
/*每个页面公共css */ |
||||
page { |
||||
background: #f4f4f4; |
||||
color: #383838; |
||||
font-size: 26rpx; |
||||
} |
||||
/* image{ |
||||
background: skyblue; |
||||
} */ |
||||
image, |
||||
video { |
||||
display: block; |
||||
} |
||||
image{will-change: transform} |
||||
/*字体颜色*/ |
||||
.cor_fff { |
||||
color: #fff; |
||||
} |
||||
.cor_000 { |
||||
color: #000; |
||||
} |
||||
.cor_red { |
||||
color: #eb3831; |
||||
} |
||||
.cor_333 { |
||||
color: #333; |
||||
} |
||||
.cor_666 { |
||||
color: #666; |
||||
} |
||||
.cor_999 { |
||||
color: #999; |
||||
} |
||||
.cor_ccc { |
||||
color: #ccc; |
||||
} |
||||
.cor_eee{ |
||||
color: #eee; |
||||
} |
||||
.cor_text { |
||||
color: #262626; |
||||
} |
||||
.cor_38{ |
||||
color: #383838; |
||||
} |
||||
.cor_8c { |
||||
color: #8c8c8c; |
||||
} |
||||
.cor_80 { |
||||
color: #808080; |
||||
} |
||||
.cor_A6 { |
||||
color: #A6A6A6; |
||||
} |
||||
.cor_theme { |
||||
color: $uni-color-primary !important; |
||||
} |
||||
.cor_blue { |
||||
color: #43aefd; |
||||
} |
||||
.bk_theme { |
||||
background-color: $uni-color-primary !important; |
||||
color: #fff !important; |
||||
} |
||||
|
||||
.fwb { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
/**图片大小**/ |
||||
.img16 { |
||||
width: 16rpx; |
||||
height: 16rpx; |
||||
} |
||||
|
||||
.mt70 { |
||||
margin-top: 70rpx; |
||||
} |
||||
.mt80 { |
||||
margin-top: 80rpx; |
||||
} |
||||
.mt90 { |
||||
margin-top: 90rpx; |
||||
} |
||||
|
||||
.mr60 { |
||||
margin-right: 60rpx; |
||||
} |
||||
.mr70 { |
||||
margin-right: 70rpx; |
||||
} |
||||
.mr100 { |
||||
margin-right: 100rpx; |
||||
} |
||||
.m20lr { |
||||
margin-left: 20rpx; |
||||
margin-right: 20rpx; |
||||
} |
||||
.m30lr { |
||||
margin-left: 30rpx; |
||||
margin-right: 30rpx; |
||||
} |
||||
|
||||
/**border**/ |
||||
.bb1 { |
||||
border-bottom: solid 1px #f4f4f4; |
||||
} |
||||
.bt1 { |
||||
border-top: solid 1px #eee; |
||||
} |
||||
.border1 { |
||||
border: solid 1px #eee; |
||||
} |
||||
.border_tb1{ |
||||
border-bottom: solid 1px #eee; |
||||
border-top: solid 1px #eee; |
||||
} |
||||
/***背景**/ |
||||
.bk_f { |
||||
background-color: #fff; |
||||
} |
||||
.bk_f9 { |
||||
background-color: #f9f9f9; |
||||
} |
||||
.bk_f2 { |
||||
background-color: #f2f2f2; |
||||
} |
||||
.bk_rgba05 { |
||||
background-color: rgba(0, 0, 0, 0.5); |
||||
} |
||||
.bk_main { |
||||
background-color: #3d92e1; |
||||
} |
||||
.bk_blue { |
||||
background: skyblue; |
||||
} |
||||
.bk_red { |
||||
background-color: #ff2d17; |
||||
} |
||||
.bk_white { |
||||
background-color: #fff; |
||||
} |
||||
.opt5 { |
||||
opacity: 0.5; |
||||
} |
||||
|
||||
.br_p50 { |
||||
border-radius: 50%; |
||||
} |
||||
.br_ltb{ |
||||
border-radius: 50% 0 0 50% !important; |
||||
} |
||||
.br_rtb{ |
||||
border-radius: 0 50% 50% 0 !important; |
||||
} |
||||
|
||||
/**宽度**/ |
||||
.wp20 { |
||||
width: 20%; |
||||
} |
||||
.wp25 { |
||||
width: 25%; |
||||
} |
||||
.wp40 { |
||||
width: 40%; |
||||
} |
||||
.wp50 { |
||||
width: 50%; |
||||
} |
||||
.wp100 { |
||||
width: 100%; |
||||
} |
||||
|
||||
/**高度**/ |
||||
.hp100 { |
||||
height: 100%; |
||||
} |
||||
|
||||
/**行距**/ |
||||
.lh1 { |
||||
line-height: 1; |
||||
} |
||||
.lh50{ |
||||
line-height: 50rpx; |
||||
} |
||||
/**flex 设置**/ |
||||
.df { |
||||
display: flex; |
||||
} |
||||
.fldr { |
||||
flex-direction: row !important; |
||||
} |
||||
.fldc { |
||||
flex-direction: column !important; |
||||
} |
||||
.fldrr { |
||||
flex-direction: row-reverse !important; |
||||
} |
||||
.jcsb { |
||||
justify-content: space-between !important; |
||||
} |
||||
.jcsba { |
||||
justify-content: space-around !important; |
||||
} |
||||
.jcfs { |
||||
justify-content: flex-start !important; |
||||
} |
||||
.jcc { |
||||
justify-content: center !important; |
||||
} |
||||
.jcfe { |
||||
justify-content: flex-end; |
||||
} |
||||
.fl1 { |
||||
flex: 1 !important; |
||||
} |
||||
.fw { |
||||
flex-wrap: wrap; |
||||
} |
||||
.ai-center { |
||||
align-items: center !important; |
||||
} |
||||
.ai-start { |
||||
align-items: flex-start; |
||||
} |
||||
.ai-baseline { |
||||
align-items: flex-end; |
||||
} |
||||
.ai-end { |
||||
align-items: flex-end; |
||||
} |
||||
.fls0 { |
||||
flex-shrink: 0; |
||||
} |
||||
|
||||
/*对齐*/ |
||||
.tac { |
||||
text-align: center; |
||||
} |
||||
.tar { |
||||
text-align: right; |
||||
} |
||||
.tal { |
||||
text-align: left; |
||||
} |
||||
|
||||
/** 其他样式 **/ |
||||
.ov { |
||||
overflow: hidden; |
||||
} |
||||
.ovya { |
||||
overflow-y: auto; |
||||
} |
||||
.bsb { |
||||
box-sizing: border-box; |
||||
} |
||||
.re { |
||||
position: relative; |
||||
} |
||||
.ab { |
||||
position: absolute; |
||||
} |
||||
.ab_full { |
||||
position: absolute; |
||||
left: 0; |
||||
top: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
} |
||||
.fixed { |
||||
position: fixed; |
||||
} |
||||
.db { |
||||
display: block; |
||||
} |
||||
.di { |
||||
display: inline; |
||||
} |
||||
.dib { |
||||
display: inline-block; |
||||
} |
||||
.dif { |
||||
display: inline-flex; |
||||
} |
||||
.v-middle { |
||||
position: absolute; |
||||
top: 50%; |
||||
transform: translateY(-50%); |
||||
} |
||||
.h-middle { |
||||
position: absolute; |
||||
left: 50%; |
||||
transform: translateX(-50%); |
||||
} |
||||
.middle { |
||||
position: absolute; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
} |
||||
.text-line-through { |
||||
text-decoration: line-through; |
||||
} |
||||
.text-underline { |
||||
text-decoration: underline; |
||||
} |
||||
.wb { |
||||
display: block; |
||||
word-break: break-all; |
||||
word-wrap: break-word; |
||||
} |
||||
.vam { |
||||
vertical-align: middle; |
||||
} |
||||
|
||||
/*灰色分割条*/ |
||||
.gray_bar { |
||||
height: 20rpx; |
||||
background: #f5f5f5; |
||||
} |
||||
|
||||
.scroll-box { |
||||
flex: 1; |
||||
height: 100%; |
||||
position: relative; |
||||
} |
||||
.content_box { |
||||
flex: 1; |
||||
overflow-y: auto; |
||||
} |
||||
.theme-tag { |
||||
display: inline-block; |
||||
padding: 0 25rpx; |
||||
height: 36rpx; |
||||
line-height: 36rpx; |
||||
border-radius: 20rpx; |
||||
border: 2rpx solid $uni-color-primary; |
||||
box-sizing: content-box; |
||||
font-size: 24rpx; |
||||
color: $uni-color-primary; |
||||
} |
||||
.selector { |
||||
position: relative; |
||||
padding-left: 30rpx; |
||||
padding-right: 20rpx; |
||||
height: 60rpx; |
||||
line-height: 60rpx; |
||||
border-radius: 30rpx; |
||||
font-size: 24rpx; |
||||
color: #333; |
||||
background-color: #fff; |
||||
} |
||||
.selector.actived { |
||||
color: $uni-color-primary; |
||||
border: 1rpx solid $uni-color-primary; |
||||
background: #e6f2ff; |
||||
} |
||||
|
||||
|
||||
.img24 { |
||||
width: 24px; |
||||
height: 24px; |
||||
} |
||||
|
||||
.img36 { |
||||
width: 36px; |
||||
height: 36px; |
||||
} |
||||
|
||||
.img48 { |
||||
width: 48px; |
||||
height: 48px; |
||||
} |
||||
|
||||
.img50 { |
||||
width: 50px; |
||||
height: 50px; |
||||
} |
||||
|
||||
.img120 { |
||||
width: 120px; |
||||
height: 120px; |
||||
} |
||||
|
||||
.img140 { |
||||
width: 140px; |
||||
height: 140px; |
||||
} |
||||
|
||||
.theme-bg-light { |
||||
background-color: $uni-color-primary; |
||||
} |
||||
.pt100 { |
||||
padding-top: 100rpx; |
||||
} |
||||
|
||||
.theme-btn { |
||||
padding: 0 20px; |
||||
min-width: 250rpx; |
||||
height: 78rpx; |
||||
line-height: 80rpx; |
||||
text-align: center; |
||||
font-size: 28rpx; |
||||
color: #fff; |
||||
border-radius: 40rpx; |
||||
background-color: $uni-color-primary; |
||||
} |
||||
|
||||
.theme-btn-light { |
||||
padding: 0 20px; |
||||
min-width: 250rpx; |
||||
height: 78rpx; |
||||
line-height: 80rpx; |
||||
text-align: center; |
||||
font-size: 28rpx; |
||||
color: #fff; |
||||
border-radius: 40rpx; |
||||
color: $uni-color-primary; |
||||
border: 1px solid $uni-color-primary; |
||||
} |
||||
|
||||
.img70 { |
||||
width:70px; |
||||
height: 70px; |
||||
} |
||||
|
||||
// .u-tabbar { |
||||
// max-height: 50px; |
||||
// } |
||||
@ -0,0 +1,678 @@ |
||||
html, |
||||
body, |
||||
#app { |
||||
box-sizing: border-box; |
||||
height: 100%; |
||||
font-family: Avenir, Helvetica, Arial, sans-serif; |
||||
} |
||||
|
||||
/* 背景 */ |
||||
.bc-000 { |
||||
background-color: #000; |
||||
} |
||||
.bc-f5 { |
||||
background-color: #f5f5f5; |
||||
} |
||||
.bc-f7 { |
||||
background-color: #f7f7f7; |
||||
} |
||||
.bc-fff { |
||||
background-color: #fff; |
||||
} |
||||
.bc-t { |
||||
background-color: transparent; |
||||
} |
||||
|
||||
/* border */ |
||||
.border-top { |
||||
border-top: solid 1px #eee; |
||||
} |
||||
.border-bottom { |
||||
border-bottom: solid 1px #eee; |
||||
} |
||||
.border-0 { |
||||
border: 0px; |
||||
} |
||||
.border-1 { |
||||
border: 1px solid #eee; |
||||
} |
||||
.border-2 { |
||||
border: 2px solid #eee; |
||||
} |
||||
.border-3 { |
||||
border: 3px solid #eee; |
||||
} |
||||
.border-4 { |
||||
border: 4px solid #eee; |
||||
} |
||||
.border-5 { |
||||
border: 5px solid #eee; |
||||
} |
||||
|
||||
/* 文本格式 */ |
||||
.i { |
||||
word-wrap: break-word; |
||||
text-align: justify; |
||||
text-justify: inter-ideograph; |
||||
} |
||||
|
||||
/* 字体颜色 */ |
||||
.cor-000 { |
||||
color: #000000; |
||||
} |
||||
.cor-333 { |
||||
color: #333333; |
||||
} |
||||
.cor-666 { |
||||
color: #666666; |
||||
} |
||||
.cor-999 { |
||||
color: #999999; |
||||
} |
||||
.cor-aaa { |
||||
color: #aaaaaa; |
||||
} |
||||
.cor-ccc { |
||||
color: #cccccc; |
||||
} |
||||
.cor-ddd { |
||||
color: #dddddd; |
||||
} |
||||
.cor-fff { |
||||
color: #ffffff; |
||||
} |
||||
.cor-price { |
||||
color: #C03639; |
||||
} |
||||
|
||||
/* 行距 */ |
||||
.lh10 { |
||||
line-height: 1; |
||||
} |
||||
.lh11 { |
||||
line-height: 1.1; |
||||
} |
||||
.lh12 { |
||||
line-height: 1.2; |
||||
} |
||||
.lh13 { |
||||
line-height: 1.3; |
||||
} |
||||
.lh14 { |
||||
line-height: 1.4; |
||||
} |
||||
.lh15 { |
||||
line-height: 1.5; |
||||
} |
||||
.lh16 { |
||||
line-height: 1.6; |
||||
} |
||||
.lh18 { |
||||
line-height: 1.8; |
||||
} |
||||
.lh20 { |
||||
line-height: 2; |
||||
} |
||||
.lh25 { |
||||
line-height: 2.5; |
||||
} |
||||
.lh30 { |
||||
line-height: 3; |
||||
} |
||||
|
||||
/* 高度 */ |
||||
.hvh100 { |
||||
height: 100vh; |
||||
overflow: hidden; |
||||
} |
||||
.hp100 { |
||||
height: 100%; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
/* float */ |
||||
.float-l { |
||||
float: left; |
||||
} |
||||
.float-r { |
||||
float: right; |
||||
} |
||||
.clearfix-both::after { |
||||
display: table; |
||||
clear: both; |
||||
content: ''; |
||||
} |
||||
|
||||
/* flex */ |
||||
.flex { |
||||
display: flex; |
||||
} |
||||
.fl0 { |
||||
flex: 0; |
||||
} |
||||
.fl1 { |
||||
flex: 1; |
||||
} |
||||
.fl2 { |
||||
flex: 2; |
||||
} |
||||
.fl3 { |
||||
flex: 3; |
||||
} |
||||
.fl4 { |
||||
flex: 4; |
||||
} |
||||
.fld-r { |
||||
flex-direction: row; |
||||
} |
||||
.fld-c { |
||||
flex-direction: column; |
||||
} |
||||
.fld-rr { |
||||
flex-direction: row-reverse; |
||||
} |
||||
.flw-w { |
||||
flex-wrap: wrap; |
||||
} |
||||
.fls0 { |
||||
flex-shrink: 0; |
||||
} |
||||
|
||||
.jc-sb { |
||||
justify-content: space-between; |
||||
} |
||||
.jc-sa { |
||||
justify-content: space-around; |
||||
} |
||||
.jc-fs { |
||||
justify-content: flex-start; |
||||
} |
||||
.jc-c { |
||||
justify-content: center; |
||||
} |
||||
.jc-fe { |
||||
justify-content: flex-end; |
||||
} |
||||
|
||||
.ai-c { |
||||
align-items: center; |
||||
} |
||||
.ai-s { |
||||
align-items: stretch; |
||||
} |
||||
.ai-fs { |
||||
align-items: flex-start; |
||||
} |
||||
.ai-fe { |
||||
align-items: flex-end; |
||||
} |
||||
|
||||
/* 对齐 */ |
||||
.text-center { |
||||
text-align: center; |
||||
} |
||||
.text-right { |
||||
text-align: right; |
||||
} |
||||
.text-left { |
||||
text-align: left; |
||||
} |
||||
|
||||
/* display */ |
||||
.none { |
||||
display: none; |
||||
} |
||||
.block { |
||||
display: block; |
||||
} |
||||
.inline { |
||||
display: inline; |
||||
} |
||||
.inline-block { |
||||
display: inline-block; |
||||
} |
||||
|
||||
/* 溢出 */ |
||||
.text-ellipsis { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
} |
||||
.text-ellipsis2 { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
display: -webkit-box; |
||||
-webkit-box-orient: vertical; |
||||
-webkit-line-clamp: 2; |
||||
} |
||||
.text-ellipsis3 { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
display: -webkit-box; |
||||
-webkit-box-orient: vertical; |
||||
-webkit-line-clamp: 3; |
||||
} |
||||
|
||||
/* 其他 */ |
||||
.middle { |
||||
position: absolute; |
||||
z-index: 2; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%, -50%); |
||||
} |
||||
.middle-y { |
||||
position: absolute; |
||||
z-index: 2; |
||||
top: 50%; |
||||
transform: translateY(-50%); |
||||
} |
||||
.middle-x { |
||||
position: absolute; |
||||
z-index: 2; |
||||
left: 50%; |
||||
transform: translateX(-50%); |
||||
} |
||||
.text-line-through { |
||||
text-decoration: line-through; |
||||
} |
||||
.text-underline { |
||||
text-decoration: underline; |
||||
} |
||||
.overflow-h { |
||||
overflow: hidden; |
||||
} |
||||
.overflow-y { |
||||
overflow-y: auto; |
||||
overflow-x: hidden; |
||||
} |
||||
.overflow-x { |
||||
overflow-x: auto; |
||||
overflow-y: hidden; |
||||
} |
||||
.border-box { |
||||
box-sizing: border-box; |
||||
} |
||||
.content-box { |
||||
box-sizing: content-box; |
||||
} |
||||
.relative { |
||||
position: relative; |
||||
z-index: 1; |
||||
} |
||||
.absolute { |
||||
position: absolute; |
||||
z-index: 2; |
||||
} |
||||
.absolute-full { |
||||
position: absolute; |
||||
z-index: 2; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
} |
||||
.fixed { |
||||
position: fixed; |
||||
z-index: 9; |
||||
} |
||||
.break-all { |
||||
display: block; |
||||
word-break: break-all; |
||||
word-wrap: break-word; |
||||
} |
||||
.nowrap { |
||||
white-space: nowrap; |
||||
} |
||||
.va-m { |
||||
vertical-align: middle; |
||||
} |
||||
.va-t { |
||||
vertical-align: top; |
||||
} |
||||
.va-b { |
||||
vertical-align: bottom; |
||||
} |
||||
|
||||
.opacity0 { |
||||
opacity: 0; |
||||
} |
||||
.opacity1 { |
||||
opacity: 0.1; |
||||
} |
||||
.opacity2 { |
||||
opacity: 0.2; |
||||
} |
||||
.opacity3 { |
||||
opacity: 0.3; |
||||
} |
||||
.opacity4 { |
||||
opacity: 0.4; |
||||
} |
||||
.opacity5 { |
||||
opacity: 0.5; |
||||
} |
||||
.opacity6 { |
||||
opacity: 0.6; |
||||
} |
||||
.opacity7 { |
||||
opacity: 0.7; |
||||
} |
||||
.opacity8 { |
||||
opacity: 0.8; |
||||
} |
||||
.opacity9 { |
||||
opacity: 0.9; |
||||
} |
||||
.opacity10 { |
||||
opacity: 1; |
||||
} |
||||
|
||||
/* z-index */ |
||||
.z-index-1 { |
||||
z-index: -1; |
||||
} |
||||
.z-index0 { |
||||
z-index: 0; |
||||
} |
||||
.z-index1 { |
||||
z-index: 1; |
||||
} |
||||
.z-index2 { |
||||
z-index: 2; |
||||
} |
||||
.z-index3 { |
||||
z-index: 3; |
||||
} |
||||
.z-index4 { |
||||
z-index: 4; |
||||
} |
||||
.z-index5 { |
||||
z-index: 5; |
||||
} |
||||
.z-index6 { |
||||
z-index: 6; |
||||
} |
||||
.z-index7 { |
||||
z-index: 7; |
||||
} |
||||
.z-index8 { |
||||
z-index: 8; |
||||
} |
||||
.z-index9 { |
||||
z-index: 9; |
||||
} |
||||
.z-index10 { |
||||
z-index: 10; |
||||
} |
||||
.z-index15 { |
||||
z-index: 15; |
||||
} |
||||
.z-index19 { |
||||
z-index: 19; |
||||
} |
||||
.z-index20 { |
||||
z-index: 20; |
||||
} |
||||
.z-index99 { |
||||
z-index: 99; |
||||
} |
||||
.z-index100 { |
||||
z-index: 100; |
||||
} |
||||
.z-index300 { |
||||
z-index: 300; |
||||
} |
||||
.z-index500 { |
||||
z-index: 500; |
||||
} |
||||
.z-index999 { |
||||
z-index: 999; |
||||
} |
||||
|
||||
/* 宽度 */ |
||||
.wp20 { |
||||
width: 20%; |
||||
} |
||||
.wp23 { |
||||
width: 23%; |
||||
} |
||||
.wp24 { |
||||
width: 24%; |
||||
} |
||||
.wp25 { |
||||
width: 25%; |
||||
} |
||||
.wp30 { |
||||
width: 30%; |
||||
} |
||||
.wp31 { |
||||
width: 31%; |
||||
} |
||||
.wp32 { |
||||
width: 32%; |
||||
} |
||||
.wp33 { |
||||
width: 33%; |
||||
} |
||||
.wp40 { |
||||
width: 40%; |
||||
} |
||||
.wp45 { |
||||
width: 45%; |
||||
} |
||||
.wp48 { |
||||
width: 48%; |
||||
} |
||||
.wp49 { |
||||
width: 49%; |
||||
} |
||||
.wp50 { |
||||
width: 50%; |
||||
} |
||||
.wp60 { |
||||
width: 60%; |
||||
} |
||||
.wp70 { |
||||
width: 70%; |
||||
} |
||||
.wp80 { |
||||
width: 80%; |
||||
} |
||||
.wp85 { |
||||
width: 85%; |
||||
} |
||||
.wp90 { |
||||
width: 90%; |
||||
} |
||||
.wp95 { |
||||
width: 95%; |
||||
} |
||||
.wp100 { |
||||
width: 100%; |
||||
} |
||||
.wvw100 { |
||||
width: 100vw; |
||||
} |
||||
.wp100-i { |
||||
width: 100% !important; |
||||
} |
||||
|
||||
/* 字体样式 */ |
||||
.arial { |
||||
font-family: Arial; |
||||
} |
||||
|
||||
/* 字体大小 */ |
||||
@for $i from 0 through 100 { |
||||
.fs#{$i} { |
||||
font-size: $i + px; |
||||
} |
||||
.img#{$i} { |
||||
width: $i + px; |
||||
height: $i + px; |
||||
} |
||||
} |
||||
|
||||
.fw400 { |
||||
font-weight: 400; |
||||
} |
||||
.fw500 { |
||||
font-weight: 500; |
||||
} |
||||
.fw600 { |
||||
font-weight: 600; |
||||
} |
||||
.fw700 { |
||||
font-weight: 700; |
||||
} |
||||
.fw800 { |
||||
font-weight: 800; |
||||
} |
||||
.fw900 { |
||||
font-weight: 900; |
||||
} |
||||
|
||||
.m0a { |
||||
margin: 0 auto; |
||||
} |
||||
|
||||
.br-p50 { |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
/* marging */ |
||||
.mt-3 { |
||||
margin-top: -3px; |
||||
} |
||||
.mt-2 { |
||||
margin-top: -2px; |
||||
} |
||||
.mt-1 { |
||||
margin-top: -1px; |
||||
} |
||||
|
||||
.mr-3 { |
||||
margin-right: -3px; |
||||
} |
||||
.mr-2 { |
||||
margin-right: -2px; |
||||
} |
||||
.mr-1 { |
||||
margin-right: -1px; |
||||
} |
||||
|
||||
.mr-p1 { |
||||
margin-right: 1%; |
||||
} |
||||
.mr-p2 { |
||||
margin-right: 2%; |
||||
} |
||||
.mr-p3 { |
||||
margin-right: 3%; |
||||
} |
||||
.mr-p4 { |
||||
margin-right: 4%; |
||||
} |
||||
.mr-p5 { |
||||
margin-right: 5%; |
||||
} |
||||
|
||||
.mb-5 { |
||||
margin-bottom: -5px; |
||||
} |
||||
.mb-4 { |
||||
margin-bottom: -4px; |
||||
} |
||||
.mb-3 { |
||||
margin-bottom: -3px; |
||||
} |
||||
.mb-2 { |
||||
margin-bottom: -2px; |
||||
} |
||||
.mb-1 { |
||||
margin-bottom: -1px; |
||||
} |
||||
|
||||
.ml-5 { |
||||
margin-left: -5px; |
||||
} |
||||
.ml-4 { |
||||
margin-left: -4px; |
||||
} |
||||
.ml-3 { |
||||
margin-left: -3px; |
||||
} |
||||
.ml-2 { |
||||
margin-left: -2px; |
||||
} |
||||
.ml-1 { |
||||
margin-left: -1px; |
||||
} |
||||
|
||||
@for $i from 0 through 50 { |
||||
.m#{$i} { |
||||
margin: $i + px; |
||||
} |
||||
.mt#{$i} { |
||||
margin-top: $i + px; |
||||
} |
||||
.ml#{$i} { |
||||
margin-left: $i + px; |
||||
} |
||||
.mb#{$i} { |
||||
margin-bottom: $i + px; |
||||
} |
||||
.mr#{$i} { |
||||
margin-right: $i + px; |
||||
} |
||||
.m#{$i}tb { |
||||
margin-top: $i + px; |
||||
margin-bottom: $i + px; |
||||
} |
||||
.m#{$i}lr { |
||||
margin-left: $i + px; |
||||
margin-right: $i + px; |
||||
} |
||||
.p#{$i} { |
||||
padding: $i + px; |
||||
} |
||||
.pt#{$i} { |
||||
padding-top: $i + px; |
||||
} |
||||
.pl#{$i} { |
||||
padding-left: $i + px; |
||||
} |
||||
.pb#{$i} { |
||||
padding-bottom: $i + px; |
||||
} |
||||
.pr#{$i} { |
||||
padding-right: $i + px; |
||||
} |
||||
.p#{$i}lr { |
||||
padding-left: $i + px; |
||||
padding-right: $i + px; |
||||
} |
||||
.p#{$i}tb { |
||||
padding-top: $i + px; |
||||
padding-bottom: $i + px; |
||||
} |
||||
.br#{$i} { |
||||
border-radius: $i + px; |
||||
} |
||||
} |
||||
|
||||
@for $i from -10 through 20 { |
||||
.top#{$i} { |
||||
top: $i + px; |
||||
} |
||||
.left#{$i} { |
||||
left: $i + px; |
||||
} |
||||
.right#{$i} { |
||||
right: $i + px; |
||||
} |
||||
.bottom#{$i} { |
||||
bottom: $i + px; |
||||
} |
||||
} |
||||
@ -0,0 +1,121 @@ |
||||
|
||||
@import '@/static/style/colorui.css'; |
||||
@import './base.scss'; |
||||
@import './app.scss'; |
||||
page { |
||||
-webkit-overflow-scrolling: touch; // ios滑动不流畅 |
||||
height: 100%; |
||||
background: #f6f6f6; |
||||
width: 100%; |
||||
font-size: 30rpx; |
||||
font-family: Arial; |
||||
word-break: break-all; //英文文本不换行 |
||||
white-space: normal; |
||||
color: $u-main-color; |
||||
// padding-bottom: constant(safe-area-inset-bottom); |
||||
// padding-bottom: env(safe-area-inset-bottom); |
||||
// box-sizing: content-box; |
||||
} |
||||
|
||||
/* #ifdef MP-WEIXIN || APP-PLUS */ |
||||
::-webkit-scrollbar { |
||||
display: none; |
||||
width: 0 !important; |
||||
height: 0 !important; |
||||
-webkit-appearance: none; |
||||
background: transparent; |
||||
color: transparent; |
||||
} |
||||
/* #endif */ |
||||
|
||||
uni-tabbar.uni-tabbar-bottom .uni-tabbar { |
||||
padding-bottom: constant(safe-area-inset-bottom); |
||||
padding-bottom: env(safe-area-inset-bottom); |
||||
box-sizing: content-box; |
||||
} |
||||
.box-safe-area { |
||||
padding-bottom: constant(safe-area-inset-bottom); |
||||
padding-bottom: env(safe-area-inset-bottom); |
||||
box-sizing: content-box; |
||||
} |
||||
|
||||
.u-tabs .uni-scroll-view::-webkit-scrollbar { |
||||
width: 0; |
||||
height: 0; |
||||
color: transparent; |
||||
display: none !important; |
||||
} |
||||
|
||||
.content-wrapper { |
||||
padding: 25rpx; |
||||
} |
||||
|
||||
.input-placeholder { |
||||
font-size: 28rpx !important; |
||||
color: #a6a6a6 !important; |
||||
font-weight: 400 !important; |
||||
} |
||||
.uni-input-input, .uni-textarea-textarea, { |
||||
font-size: 28rpx; |
||||
color: #333; |
||||
} |
||||
.value-form .u-form-item__body { |
||||
flex-wrap: wrap !important; |
||||
align-items: flex-start !important; |
||||
} |
||||
.u-form-item__body__right__message { |
||||
margin-left: 0 !important; |
||||
text-align: center; |
||||
} |
||||
.u-form-item__body { |
||||
padding-top: 30rpx !important; |
||||
padding-bottom: 30rpx !important; |
||||
.u-form-item__body__left { |
||||
margin-right: 30rpx !important; |
||||
} |
||||
.u-form-item__body__right { |
||||
font-size: 28rpx !important; |
||||
} |
||||
} |
||||
|
||||
.form-label { |
||||
font-size: 28rpx; |
||||
color: #666; |
||||
line-height: 1; |
||||
} |
||||
.form-value { |
||||
font-size: 28rpx; |
||||
color: #000; |
||||
line-height: 40rpx; |
||||
} |
||||
|
||||
.u-tabs__wrapper__nav__line { |
||||
bottom: 0 !important; |
||||
} |
||||
|
||||
.u-notice .uicon-volume { |
||||
font-size: 40rpx !important; |
||||
} |
||||
|
||||
.uni-modal__btn_primary { |
||||
color: $uni-color-primary !important; |
||||
} |
||||
|
||||
.u-empty__text { |
||||
font-size: 30rpx !important; |
||||
} |
||||
|
||||
.notice .u-icon__icon { |
||||
color: $uni-color-primary !important; |
||||
} |
||||
|
||||
.u-popup { |
||||
flex: none !important; |
||||
.u-popup__content__close { |
||||
padding: 15rpx; |
||||
} |
||||
} |
||||
|
||||
input[type='password'] { |
||||
font-size: 14px; |
||||
} |
||||
@ -0,0 +1,76 @@ |
||||
/** |
||||
* 这里是uni-app内置的常用样式变量 |
||||
* |
||||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 |
||||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App |
||||
* |
||||
*/ |
||||
|
||||
/** |
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 |
||||
* |
||||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 |
||||
*/ |
||||
|
||||
/* 颜色变量 */ |
||||
@import '@/uni_modules/uview-plus/theme.scss'; |
||||
/* 行为相关颜色 */ |
||||
$uni-color-primary: #007aff; |
||||
$uni-color-success: #4cd964; |
||||
$uni-color-warning: #f0ad4e; |
||||
$uni-color-error: #dd524d; |
||||
|
||||
/* 文字基本颜色 */ |
||||
$uni-text-color:#333;//基本色 |
||||
$uni-text-color-inverse:#fff;//反色 |
||||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 |
||||
$uni-text-color-placeholder: #808080; |
||||
$uni-text-color-disable:#c0c0c0; |
||||
|
||||
/* 背景颜色 */ |
||||
$uni-bg-color:#ffffff; |
||||
$uni-bg-color-grey:#f8f8f8; |
||||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色 |
||||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 |
||||
|
||||
/* 边框颜色 */ |
||||
$uni-border-color:#c8c7cc; |
||||
|
||||
/* 尺寸变量 */ |
||||
|
||||
/* 文字尺寸 */ |
||||
$uni-font-size-sm:24rpx; |
||||
$uni-font-size-base:28rpx; |
||||
$uni-font-size-lg:32rpx; |
||||
|
||||
/* 图片尺寸 */ |
||||
$uni-img-size-sm:40rpx; |
||||
$uni-img-size-base:52rpx; |
||||
$uni-img-size-lg:80rpx; |
||||
|
||||
/* Border Radius */ |
||||
$uni-border-radius-sm: 4rpx; |
||||
$uni-border-radius-base: 6rpx; |
||||
$uni-border-radius-lg: 12rpx; |
||||
$uni-border-radius-circle: 50%; |
||||
|
||||
/* 水平间距 */ |
||||
$uni-spacing-row-sm: 10px; |
||||
$uni-spacing-row-base: 20rpx; |
||||
$uni-spacing-row-lg: 30rpx; |
||||
|
||||
/* 垂直间距 */ |
||||
$uni-spacing-col-sm: 8rpx; |
||||
$uni-spacing-col-base: 16rpx; |
||||
$uni-spacing-col-lg: 24rpx; |
||||
|
||||
/* 透明度 */ |
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 |
||||
|
||||
/* 文章场景相关 */ |
||||
$uni-color-title: #2C405A; // 文章标题颜色 |
||||
$uni-font-size-title:40rpx; |
||||
$uni-color-subtitle: #555555; // 二级标题颜色 |
||||
$uni-font-size-subtitle:36rpx; |
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色 |
||||
$uni-font-size-paragraph:30rpx; |
||||
@ -0,0 +1,21 @@ |
||||
MIT License |
||||
|
||||
Copyright (c) 2020 www.uviewui.com |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
||||
@ -0,0 +1,64 @@ |
||||
<p align="center"> |
||||
<img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;"> |
||||
</p> |
||||
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uview-plus 3.0</h3> |
||||
<h3 align="center">多平台快速开发的UI框架</h3> |
||||
|
||||
[](https://github.com/ijry/uview-plus) |
||||
[](https://github.com/ijry/uview-plus) |
||||
[](https://github.com/ijry/uview-plus/issues) |
||||
[](https://gitee.com/uiadmin/uview-plus/releases) |
||||
[](https://en.wikipedia.org/wiki/MIT_License) |
||||
|
||||
## 说明 |
||||
|
||||
uview-plus,是uni-app全面兼容vue3/nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。 |
||||
|
||||
## [官方文档:https://uiadmin.net/uview-plus](https://uiadmin.net/uview-plus) |
||||
|
||||
|
||||
## 预览 |
||||
|
||||
您可以通过**微信**扫码,查看最佳的演示效果。 |
||||
<br> |
||||
<br> |
||||
<img src="https://uiadmin.net/uview-plus/common/h5_qrcode.png" width="220" height="220" > |
||||
|
||||
## 链接 |
||||
|
||||
- [官方文档](https://uiadmin.net/uview-plus) |
||||
- [更新日志](https://uiadmin.net/uview-plus/components/changelog.html) |
||||
- [升级指南](https://uiadmin.net/uview-plus/components/changeGuide.html) |
||||
- [关于我们](https://uiadmin.net/uview-plus/cooperation/about.html) |
||||
|
||||
## 交流反馈 |
||||
|
||||
欢迎加入我们的QQ群交流反馈:[点此跳转](https://uiadmin.net/uview-plus/components/addQQGroup.html) |
||||
|
||||
## 关于PR |
||||
|
||||
> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uview-plus是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。 |
||||
> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢! |
||||
|
||||
## 安装 |
||||
|
||||
#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?name=uview-plus](https://ext.dcloud.net.cn/plugin?name=uview-plus) |
||||
|
||||
请通过[官网安装文档](https://uiadmin.net/uview-plus/components/install.html)了解更详细的内容 |
||||
|
||||
## 快速上手 |
||||
|
||||
请通过[快速上手](https://uiadmin.net/uview-plus/components/quickstart.html)了解更详细的内容 |
||||
|
||||
## 使用方法 |
||||
配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。 |
||||
|
||||
```html |
||||
<template> |
||||
<u-button text="按钮"></u-button> |
||||
</template> |
||||
``` |
||||
|
||||
## 版权信息 |
||||
uview-plus遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uview-plus应用到您的产品中。 |
||||
|
||||
@ -0,0 +1,22 @@ |
||||
## 3.1.31(2023-05-15) |
||||
本地累计修复一些问题提交 |
||||
## 3.0.9(2022-07-14) |
||||
修复u-search双向绑定 |
||||
## 3.0.8(2022-07-12) |
||||
修复u-tag默认宽度撑满容器 |
||||
## 3.0.7(2022-07-12) |
||||
修复u-navbar自定义插槽演示示例 |
||||
## 3.0.6(2022-07-11) |
||||
修复u-image缺少emits申明 |
||||
## 3.0.5(2022-07-11) |
||||
修复u-upload缺少emits申明 |
||||
## 3.0.4(2022-07-10) |
||||
修复u-textarea/u-input/u-datetime-picker/u-number-box/u-radio-group/u-switch/u-rate在vue3下数据绑定 |
||||
## 3.0.3(2022-07-09) |
||||
启用自建演示二维码 |
||||
## 3.0.2(2022-07-09) |
||||
修复dayjs/clipboard等导致打包报错 |
||||
## 3.0.1(2022-07-09) |
||||
增加插件市场地址 |
||||
## 3.0.0(2022-07-09) |
||||
# uview-plus(vue3)初步发布 |
||||
@ -0,0 +1,80 @@ |
||||
<template> |
||||
<uvForm |
||||
ref="uForm" |
||||
:model="model" |
||||
:rules="rules" |
||||
:errorType="errorType" |
||||
:borderBottom="borderBottom" |
||||
:labelPosition="labelPosition" |
||||
:labelWidth="labelWidth" |
||||
:labelAlign="labelAlign" |
||||
:labelStyle="labelStyle" |
||||
:customStyle="customStyle" |
||||
> |
||||
<slot /> |
||||
</uvForm> |
||||
</template> |
||||
|
||||
<script> |
||||
/** |
||||
* 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件 |
||||
* 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转 |
||||
*/ |
||||
import uvForm from '../u-form/u-form.vue'; |
||||
import props from '../u-form/props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
export default { |
||||
// #ifdef MP-WEIXIN |
||||
name: 'u-form', |
||||
// #endif |
||||
// #ifndef MP-WEIXIN |
||||
name: 'u--form', |
||||
// #endif |
||||
mixins: [mpMixin, props, mixin], |
||||
components: { |
||||
uvForm |
||||
}, |
||||
created() { |
||||
this.children = [] |
||||
}, |
||||
methods: { |
||||
// 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则 |
||||
setRules(rules) { |
||||
this.$refs.uForm.setRules(rules) |
||||
}, |
||||
validate() { |
||||
/** |
||||
* 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form |
||||
* 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的 |
||||
* 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children |
||||
*/ |
||||
// #ifdef MP-WEIXIN |
||||
this.setMpData() |
||||
// #endif |
||||
return this.$refs.uForm.validate() |
||||
}, |
||||
validateField(value, callback) { |
||||
// #ifdef MP-WEIXIN |
||||
this.setMpData() |
||||
// #endif |
||||
return this.$refs.uForm.validateField(value, callback) |
||||
}, |
||||
resetFields() { |
||||
// #ifdef MP-WEIXIN |
||||
this.setMpData() |
||||
// #endif |
||||
return this.$refs.uForm.resetFields() |
||||
}, |
||||
clearValidate(props) { |
||||
// #ifdef MP-WEIXIN |
||||
this.setMpData() |
||||
// #endif |
||||
return this.$refs.uForm.clearValidate(props) |
||||
}, |
||||
setMpData() { |
||||
this.$refs.uForm.children = this.children |
||||
} |
||||
}, |
||||
} |
||||
</script> |
||||
@ -0,0 +1,50 @@ |
||||
<template> |
||||
<uvImage |
||||
:src="src" |
||||
:mode="mode" |
||||
:width="width" |
||||
:height="height" |
||||
:shape="shape" |
||||
:radius="radius" |
||||
:lazyLoad="lazyLoad" |
||||
:showMenuByLongpress="showMenuByLongpress" |
||||
:loadingIcon="loadingIcon" |
||||
:errorIcon="errorIcon" |
||||
:showLoading="showLoading" |
||||
:showError="showError" |
||||
:fade="fade" |
||||
:webp="webp" |
||||
:duration="duration" |
||||
:bgColor="bgColor" |
||||
:customStyle="customStyle" |
||||
@click="$emit('click')" |
||||
@error="$emit('error')" |
||||
@load="$emit('load')" |
||||
> |
||||
<template v-slot:loading> |
||||
<slot name="loading"></slot> |
||||
</template> |
||||
<template v-slot:error> |
||||
<slot name="error"></slot> |
||||
</template> |
||||
</uvImage> |
||||
</template> |
||||
|
||||
<script> |
||||
/** |
||||
* 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件 |
||||
* 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转 |
||||
*/ |
||||
import uvImage from '../u-image/u-image.vue'; |
||||
import props from '../u-image/props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
export default { |
||||
name: 'u--image', |
||||
mixins: [mpMixin, props, mixin], |
||||
components: { |
||||
uvImage |
||||
}, |
||||
emits: ['click', 'error', 'load'] |
||||
} |
||||
</script> |
||||
@ -0,0 +1,74 @@ |
||||
<template> |
||||
<uvInput |
||||
<!-- #ifdef VUE2 --> |
||||
:value="value" |
||||
@input="e => $emit('input', e)" |
||||
<!-- #endif --> |
||||
<!-- #ifdef VUE3 --> |
||||
:modelValue="modelValue" |
||||
@update:modelValue="e => $emit('update:modelValue', e)" |
||||
<!-- #endif --> |
||||
:type="type" |
||||
:fixed="fixed" |
||||
:disabled="disabled" |
||||
:disabledColor="disabledColor" |
||||
:clearable="clearable" |
||||
:password="password" |
||||
:maxlength="maxlength" |
||||
:placeholder="placeholder" |
||||
:placeholderClass="placeholderClass" |
||||
:placeholderStyle="placeholderStyle" |
||||
:showWordLimit="showWordLimit" |
||||
:confirmType="confirmType" |
||||
:confirmHold="confirmHold" |
||||
:holdKeyboard="holdKeyboard" |
||||
:focus="focus" |
||||
:autoBlur="autoBlur" |
||||
:disableDefaultPadding="disableDefaultPadding" |
||||
:cursor="cursor" |
||||
:cursorSpacing="cursorSpacing" |
||||
:selectionStart="selectionStart" |
||||
:selectionEnd="selectionEnd" |
||||
:adjustPosition="adjustPosition" |
||||
:inputAlign="inputAlign" |
||||
:fontSize="fontSize" |
||||
:color="color" |
||||
:prefixIcon="prefixIcon" |
||||
:suffixIcon="suffixIcon" |
||||
:suffixIconStyle="suffixIconStyle" |
||||
:prefixIconStyle="prefixIconStyle" |
||||
:border="border" |
||||
:readonly="readonly" |
||||
:shape="shape" |
||||
:customStyle="customStyle" |
||||
:formatter="formatter" |
||||
:ignoreCompositionEvent="ignoreCompositionEvent" |
||||
> |
||||
<!-- #ifdef MP --> |
||||
<slot name="prefix"></slot> |
||||
<slot name="suffix"></slot> |
||||
<!-- #endif --> |
||||
<!-- #ifndef MP --> |
||||
<slot name="prefix" slot="prefix"></slot> |
||||
<slot name="suffix" slot="suffix"></slot> |
||||
<!-- #endif --> |
||||
</uvInput> |
||||
</template> |
||||
|
||||
<script> |
||||
/** |
||||
* 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件 |
||||
* 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转 |
||||
*/ |
||||
import uvInput from '../u-input/u-input.vue'; |
||||
import props from '../u-input/props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
export default { |
||||
name: 'u--input', |
||||
mixins: [mpMixin, props, mixin], |
||||
components: { |
||||
uvInput |
||||
}, |
||||
} |
||||
</script> |
||||
@ -0,0 +1,45 @@ |
||||
<template> |
||||
<uvText |
||||
:type="type" |
||||
:show="show" |
||||
:text="text" |
||||
:prefixIcon="prefixIcon" |
||||
:suffixIcon="suffixIcon" |
||||
:mode="mode" |
||||
:href="href" |
||||
:format="format" |
||||
:call="call" |
||||
:openType="openType" |
||||
:bold="bold" |
||||
:block="block" |
||||
:lines="lines" |
||||
:color="color" |
||||
:decoration="decoration" |
||||
:size="size" |
||||
:iconStyle="iconStyle" |
||||
:margin="margin" |
||||
:lineHeight="lineHeight" |
||||
:align="align" |
||||
:wordWrap="wordWrap" |
||||
:customStyle="customStyle" |
||||
></uvText> |
||||
</template> |
||||
|
||||
<script> |
||||
/** |
||||
* 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件 |
||||
* 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转 |
||||
* 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法 |
||||
*/ |
||||
import uvText from "../u-text/u-text.vue"; |
||||
import props from "../u-text/props.js"; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js' |
||||
import mixin from '../../libs/mixin/mixin.js' |
||||
export default { |
||||
name: "u--text", |
||||
mixins: [mpMixin, mixin, props,], |
||||
components: { |
||||
uvText, |
||||
}, |
||||
}; |
||||
</script> |
||||
@ -0,0 +1,47 @@ |
||||
<template> |
||||
<uvTextarea |
||||
:value="value" |
||||
:modelValue="modelValue" |
||||
:placeholder="placeholder" |
||||
:height="height" |
||||
:confirmType="confirmType" |
||||
:disabled="disabled" |
||||
:count="count" |
||||
:focus="focus" |
||||
:autoHeight="autoHeight" |
||||
:fixed="fixed" |
||||
:cursorSpacing="cursorSpacing" |
||||
:cursor="cursor" |
||||
:showConfirmBar="showConfirmBar" |
||||
:selectionStart="selectionStart" |
||||
:selectionEnd="selectionEnd" |
||||
:adjustPosition="adjustPosition" |
||||
:disableDefaultPadding="disableDefaultPadding" |
||||
:holdKeyboard="holdKeyboard" |
||||
:maxlength="maxlength" |
||||
:border="border" |
||||
:customStyle="customStyle" |
||||
:formatter="formatter" |
||||
:ignoreCompositionEvent="ignoreCompositionEvent" |
||||
@input="e => $emit('input', e)" |
||||
@update:modelValue="e => $emit('update:modelValue', e)" |
||||
></uvTextarea> |
||||
</template> |
||||
|
||||
<script> |
||||
/** |
||||
* 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件 |
||||
* 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转 |
||||
*/ |
||||
import uvTextarea from '../u-textarea/u-textarea.vue'; |
||||
import props from '../u-textarea/props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
export default { |
||||
name: 'u--textarea', |
||||
mixins: [mpMixin, props, mixin], |
||||
components: { |
||||
uvTextarea |
||||
}, |
||||
} |
||||
</script> |
||||
@ -0,0 +1,55 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 操作菜单是否展示 (默认false)
|
||||
show: { |
||||
type: Boolean, |
||||
default: defprops.actionSheet.show |
||||
}, |
||||
// 标题
|
||||
title: { |
||||
type: String, |
||||
default: defprops.actionSheet.title |
||||
}, |
||||
// 选项上方的描述信息
|
||||
description: { |
||||
type: String, |
||||
default: defprops.actionSheet.description |
||||
}, |
||||
// 数据
|
||||
actions: { |
||||
type: Array, |
||||
default: defprops.actionSheet.actions |
||||
}, |
||||
// 取消按钮的文字,不为空时显示按钮
|
||||
cancelText: { |
||||
type: String, |
||||
default: defprops.actionSheet.cancelText |
||||
}, |
||||
// 点击某个菜单项时是否关闭弹窗
|
||||
closeOnClickAction: { |
||||
type: Boolean, |
||||
default: defprops.actionSheet.closeOnClickAction |
||||
}, |
||||
// 处理底部安全区(默认true)
|
||||
safeAreaInsetBottom: { |
||||
type: Boolean, |
||||
default: defprops.actionSheet.safeAreaInsetBottom |
||||
}, |
||||
// 小程序的打开方式
|
||||
openType: { |
||||
type: String, |
||||
default: defprops.actionSheet.openType |
||||
}, |
||||
// 点击遮罩是否允许关闭 (默认true)
|
||||
closeOnClickOverlay: { |
||||
type: Boolean, |
||||
default: defprops.actionSheet.closeOnClickOverlay |
||||
}, |
||||
// 圆角值
|
||||
round: { |
||||
type: [Boolean, String, Number], |
||||
default: defprops.actionSheet.round |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,279 @@ |
||||
|
||||
<template> |
||||
<u-popup |
||||
:show="show" |
||||
mode="bottom" |
||||
@close="closeHandler" |
||||
:safeAreaInsetBottom="safeAreaInsetBottom" |
||||
:round="round" |
||||
> |
||||
<view class="u-action-sheet"> |
||||
<view |
||||
class="u-action-sheet__header" |
||||
v-if="title" |
||||
> |
||||
<text class="u-action-sheet__header__title u-line-1">{{title}}</text> |
||||
<view |
||||
class="u-action-sheet__header__icon-wrap" |
||||
@tap.stop="cancel" |
||||
> |
||||
<u-icon |
||||
name="close" |
||||
size="17" |
||||
color="#c8c9cc" |
||||
bold |
||||
></u-icon> |
||||
</view> |
||||
</view> |
||||
<text |
||||
class="u-action-sheet__description" |
||||
:style="[{ |
||||
marginTop: `${title && description ? 0 : '18px'}` |
||||
}]" |
||||
v-if="description" |
||||
>{{description}}</text> |
||||
<slot> |
||||
<u-line v-if="description"></u-line> |
||||
<view class="u-action-sheet__item-wrap"> |
||||
<view :key="index" v-for="(item, index) in actions"> |
||||
<!-- #ifdef MP --> |
||||
<button |
||||
class="u-reset-button" |
||||
:openType="item.openType" |
||||
@getuserinfo="onGetUserInfo" |
||||
@contact="onContact" |
||||
@getphonenumber="onGetPhoneNumber" |
||||
@error="onError" |
||||
@launchapp="onLaunchApp" |
||||
@opensetting="onOpenSetting" |
||||
:lang="lang" |
||||
:session-from="sessionFrom" |
||||
:send-message-title="sendMessageTitle" |
||||
:send-message-path="sendMessagePath" |
||||
:send-message-img="sendMessageImg" |
||||
:show-message-card="showMessageCard" |
||||
:app-parameter="appParameter" |
||||
@tap="selectHandler(index)" |
||||
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''" |
||||
> |
||||
<!-- #endif --> |
||||
<view |
||||
class="u-action-sheet__item-wrap__item" |
||||
@tap.stop="selectHandler(index)" |
||||
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''" |
||||
:hover-stay-time="150" |
||||
> |
||||
<template v-if="!item.loading"> |
||||
<text |
||||
class="u-action-sheet__item-wrap__item__name" |
||||
:style="[itemStyle(index)]" |
||||
>{{ item.name }}</text> |
||||
<text |
||||
v-if="item.subname" |
||||
class="u-action-sheet__item-wrap__item__subname" |
||||
>{{ item.subname }}</text> |
||||
</template> |
||||
<u-loading-icon |
||||
v-else |
||||
custom-class="van-action-sheet__loading" |
||||
size="18" |
||||
mode="circle" |
||||
/> |
||||
</view> |
||||
<!-- #ifdef MP --> |
||||
</button> |
||||
<!-- #endif --> |
||||
<u-line v-if="index !== actions.length - 1"></u-line> |
||||
</view> |
||||
</view> |
||||
</slot> |
||||
<u-gap |
||||
bgColor="#eaeaec" |
||||
height="6" |
||||
v-if="cancelText" |
||||
></u-gap> |
||||
<view hover-class="u-action-sheet--hover"> |
||||
<text |
||||
@touchmove.stop.prevent |
||||
:hover-stay-time="150" |
||||
v-if="cancelText" |
||||
class="u-action-sheet__cancel-text" |
||||
@tap="cancel" |
||||
>{{cancelText}}</text> |
||||
</view> |
||||
</view> |
||||
</u-popup> |
||||
</template> |
||||
|
||||
<script> |
||||
import openType from '../../libs/mixin/openType' |
||||
import button from '../../libs/mixin/button' |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* ActionSheet 操作菜单 |
||||
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/actionSheet.html |
||||
* |
||||
* @property {Boolean} show 操作菜单是否展示 (默认 false ) |
||||
* @property {String} title 操作菜单标题 |
||||
* @property {String} description 选项上方的描述信息 |
||||
* @property {Array<Object>} actions 按钮的文字数组,见官方文档示例 |
||||
* @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮 |
||||
* @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true ) |
||||
* @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true ) |
||||
* @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error ) |
||||
* @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true ) |
||||
* @property {Number|String} round 圆角值,默认无圆角 (默认 0 ) |
||||
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文 |
||||
* @property {String} sessionFrom 会话来源,openType="contact"时有效 |
||||
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效 |
||||
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效 |
||||
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效 |
||||
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false ) |
||||
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效 |
||||
* |
||||
* @event {Function} select 点击ActionSheet列表项时触发 |
||||
* @event {Function} close 点击取消按钮时触发 |
||||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效 |
||||
* @event {Function} contact 客服消息回调,openType="contact"时有效 |
||||
* @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效 |
||||
* @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效 |
||||
* @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效 |
||||
* @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效 |
||||
* @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet> |
||||
*/ |
||||
export default { |
||||
name: "u-action-sheet", |
||||
// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到 |
||||
mixins: [openType, button, mixin, props], |
||||
data() { |
||||
return { |
||||
|
||||
} |
||||
}, |
||||
computed: { |
||||
// 操作项目的样式 |
||||
itemStyle() { |
||||
return (index) => { |
||||
let style = {}; |
||||
if (this.actions[index].color) style.color = this.actions[index].color |
||||
if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize) |
||||
// 选项被禁用的样式 |
||||
if (this.actions[index].disabled) style.color = '#c0c4cc' |
||||
return style; |
||||
} |
||||
}, |
||||
}, |
||||
methods: { |
||||
closeHandler() { |
||||
// 允许点击遮罩关闭时,才发出close事件 |
||||
if(this.closeOnClickOverlay) { |
||||
this.$emit('close') |
||||
} |
||||
}, |
||||
// 点击取消按钮 |
||||
cancel() { |
||||
this.$emit('close') |
||||
}, |
||||
selectHandler(index) { |
||||
const item = this.actions[index] |
||||
if (item && !item.disabled && !item.loading) { |
||||
this.$emit('select', item) |
||||
if (this.closeOnClickAction) { |
||||
this.$emit('close') |
||||
} |
||||
} |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
$u-action-sheet-reset-button-width:100% !default; |
||||
$u-action-sheet-title-font-size: 16px !default; |
||||
$u-action-sheet-title-padding: 12px 30px !default; |
||||
$u-action-sheet-title-color: $u-main-color !default; |
||||
$u-action-sheet-header-icon-wrap-right:15px !default; |
||||
$u-action-sheet-header-icon-wrap-top:15px !default; |
||||
$u-action-sheet-description-font-size:13px !default; |
||||
$u-action-sheet-description-color:14px !default; |
||||
$u-action-sheet-description-margin: 18px 15px !default; |
||||
$u-action-sheet-item-wrap-item-padding:15px !default; |
||||
$u-action-sheet-item-wrap-name-font-size:16px !default; |
||||
$u-action-sheet-item-wrap-subname-font-size:13px !default; |
||||
$u-action-sheet-item-wrap-subname-color: #c0c4cc !default; |
||||
$u-action-sheet-item-wrap-subname-margin-top:10px !default; |
||||
$u-action-sheet-cancel-text-font-size:16px !default; |
||||
$u-action-sheet-cancel-text-color:$u-content-color !default; |
||||
$u-action-sheet-cancel-text-font-size:15px !default; |
||||
$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default; |
||||
|
||||
.u-reset-button { |
||||
width: $u-action-sheet-reset-button-width; |
||||
} |
||||
|
||||
.u-action-sheet { |
||||
text-align: center; |
||||
&__header { |
||||
position: relative; |
||||
padding: $u-action-sheet-title-padding; |
||||
&__title { |
||||
font-size: $u-action-sheet-title-font-size; |
||||
color: $u-action-sheet-title-color; |
||||
font-weight: bold; |
||||
text-align: center; |
||||
} |
||||
|
||||
&__icon-wrap { |
||||
position: absolute; |
||||
right: $u-action-sheet-header-icon-wrap-right; |
||||
top: $u-action-sheet-header-icon-wrap-top; |
||||
} |
||||
} |
||||
|
||||
&__description { |
||||
font-size: $u-action-sheet-description-font-size; |
||||
color: $u-tips-color; |
||||
margin: $u-action-sheet-description-margin; |
||||
text-align: center; |
||||
} |
||||
|
||||
&__item-wrap { |
||||
|
||||
&__item { |
||||
padding: $u-action-sheet-item-wrap-item-padding; |
||||
@include flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
flex-direction: column; |
||||
|
||||
&__name { |
||||
font-size: $u-action-sheet-item-wrap-name-font-size; |
||||
color: $u-main-color; |
||||
text-align: center; |
||||
} |
||||
|
||||
&__subname { |
||||
font-size: $u-action-sheet-item-wrap-subname-font-size; |
||||
color: $u-action-sheet-item-wrap-subname-color; |
||||
margin-top: $u-action-sheet-item-wrap-subname-margin-top; |
||||
text-align: center; |
||||
} |
||||
} |
||||
} |
||||
|
||||
&__cancel-text { |
||||
font-size: $u-action-sheet-cancel-text-font-size; |
||||
color: $u-action-sheet-cancel-text-color; |
||||
text-align: center; |
||||
padding: $u-action-sheet-cancel-text-font-size; |
||||
} |
||||
|
||||
&--hover { |
||||
background-color: $u-action-sheet-cancel-text-hover-background-color; |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,60 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 图片地址,Array<String>|Array<Object>形式
|
||||
urls: { |
||||
type: Array, |
||||
default: defprops.album.urls |
||||
}, |
||||
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
keyName: { |
||||
type: String, |
||||
default: defprops.album.keyName |
||||
}, |
||||
// 单图时,图片长边的长度
|
||||
singleSize: { |
||||
type: [String, Number], |
||||
default: defprops.album.singleSize |
||||
}, |
||||
// 多图时,图片边长
|
||||
multipleSize: { |
||||
type: [String, Number], |
||||
default: defprops.album.multipleSize |
||||
}, |
||||
// 多图时,图片水平和垂直之间的间隔
|
||||
space: { |
||||
type: [String, Number], |
||||
default: defprops.album.space |
||||
}, |
||||
// 单图时,图片缩放裁剪的模式
|
||||
singleMode: { |
||||
type: String, |
||||
default: defprops.album.singleMode |
||||
}, |
||||
// 多图时,图片缩放裁剪的模式
|
||||
multipleMode: { |
||||
type: String, |
||||
default: defprops.album.multipleMode |
||||
}, |
||||
// 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
|
||||
maxCount: { |
||||
type: [String, Number], |
||||
default: defprops.album.maxCount |
||||
}, |
||||
// 是否可以预览图片
|
||||
previewFullImage: { |
||||
type: Boolean, |
||||
default: defprops.album.previewFullImage |
||||
}, |
||||
// 每行展示图片数量,如设置,singleSize和multipleSize将会无效
|
||||
rowCount: { |
||||
type: [String, Number], |
||||
default: defprops.album.rowCount |
||||
}, |
||||
// 超出maxCount时是否显示查看更多的提示
|
||||
showMore: { |
||||
type: Boolean, |
||||
default: defprops.album.showMore |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,261 @@ |
||||
<template> |
||||
<view class="u-album"> |
||||
<view |
||||
class="u-album__row" |
||||
ref="u-album__row" |
||||
v-for="(arr, index) in showUrls" |
||||
:forComputedUse="albumWidth" |
||||
:key="index" |
||||
> |
||||
<view |
||||
class="u-album__row__wrapper" |
||||
v-for="(item, index1) in arr" |
||||
:key="index1" |
||||
:style="[imageStyle(index + 1, index1 + 1)]" |
||||
@tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''" |
||||
> |
||||
<image |
||||
:src="getSrc(item)" |
||||
:mode=" |
||||
urls.length === 1 |
||||
? imageHeight > 0 |
||||
? singleMode |
||||
: 'widthFix' |
||||
: multipleMode |
||||
" |
||||
:style="[ |
||||
{ |
||||
width: imageWidth, |
||||
height: imageHeight |
||||
} |
||||
]" |
||||
></image> |
||||
<view |
||||
v-if=" |
||||
showMore && |
||||
urls.length > rowCount * showUrls.length && |
||||
index === showUrls.length - 1 && |
||||
index1 === showUrls[showUrls.length - 1].length - 1 |
||||
" |
||||
class="u-album__row__wrapper__text" |
||||
> |
||||
<u--text |
||||
:text="`+${urls.length - maxCount}`" |
||||
color="#fff" |
||||
:size="multipleSize * 0.3" |
||||
align="center" |
||||
customStyle="justify-content: center" |
||||
></u--text> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
// #ifdef APP-NVUE |
||||
// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度 |
||||
const dom = uni.requireNativePlugin('dom') |
||||
// #endif |
||||
|
||||
/** |
||||
* Album 相册 |
||||
* @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/album.html |
||||
* |
||||
* @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式 |
||||
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址 |
||||
* @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 ) |
||||
* @property {String | Number} multipleSize 多图时,图片边长 (默认 70 ) |
||||
* @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 ) |
||||
* @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' ) |
||||
* @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' ) |
||||
* @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 ) |
||||
* @property {Boolean} previewFullImage 是否可以预览图片 (默认 true ) |
||||
* @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 ) |
||||
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true ) |
||||
* |
||||
* @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width ) |
||||
* @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album> |
||||
*/ |
||||
export default { |
||||
name: 'u-album', |
||||
mixins: [mpMixin, mixin, props], |
||||
data() { |
||||
return { |
||||
// 单图的宽度 |
||||
singleWidth: 0, |
||||
// 单图的高度 |
||||
singleHeight: 0, |
||||
// 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比 |
||||
singlePercent: 0.6 |
||||
} |
||||
}, |
||||
watch: { |
||||
urls: { |
||||
immediate: true, |
||||
handler(newVal) { |
||||
if (newVal.length === 1) { |
||||
this.getImageRect() |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
computed: { |
||||
imageStyle() { |
||||
return (index1, index2) => { |
||||
const { space, rowCount, multipleSize, urls } = this, |
||||
{ addUnit, addStyle } = uni.$u, |
||||
rowLen = this.showUrls.length, |
||||
allLen = this.urls.length |
||||
const style = { |
||||
marginRight: addUnit(space), |
||||
marginBottom: addUnit(space) |
||||
} |
||||
// 如果为最后一行,则每个图片都无需下边框 |
||||
if (index1 === rowLen) style.marginBottom = 0 |
||||
// 每行的最右边一张和总长度的最后一张无需右边框 |
||||
if ( |
||||
index2 === rowCount || |
||||
(index1 === rowLen && |
||||
index2 === this.showUrls[index1 - 1].length) |
||||
) |
||||
style.marginRight = 0 |
||||
return style |
||||
} |
||||
}, |
||||
// 将数组划分为二维数组 |
||||
showUrls() { |
||||
const arr = [] |
||||
this.urls.map((item, index) => { |
||||
// 限制最大展示数量 |
||||
if (index + 1 <= this.maxCount) { |
||||
// 计算该元素为第几个素组内 |
||||
const itemIndex = Math.floor(index / this.rowCount) |
||||
// 判断对应的索引是否存在 |
||||
if (!arr[itemIndex]) { |
||||
arr[itemIndex] = [] |
||||
} |
||||
arr[itemIndex].push(item) |
||||
} |
||||
}) |
||||
return arr |
||||
}, |
||||
imageWidth() { |
||||
return uni.$u.addUnit( |
||||
this.urls.length === 1 ? this.singleWidth : this.multipleSize |
||||
) |
||||
}, |
||||
imageHeight() { |
||||
return uni.$u.addUnit( |
||||
this.urls.length === 1 ? this.singleHeight : this.multipleSize |
||||
) |
||||
}, |
||||
// 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度 |
||||
// 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送 |
||||
albumWidth() { |
||||
let width = 0 |
||||
if (this.urls.length === 1) { |
||||
width = this.singleWidth |
||||
} else { |
||||
width = |
||||
this.showUrls[0].length * this.multipleSize + |
||||
this.space * (this.showUrls[0].length - 1) |
||||
} |
||||
this.$emit('albumWidth', width) |
||||
return width |
||||
} |
||||
}, |
||||
methods: { |
||||
// 预览图片 |
||||
onPreviewTap(url) { |
||||
const urls = this.urls.map((item) => { |
||||
return this.getSrc(item) |
||||
}) |
||||
uni.previewImage({ |
||||
current: url, |
||||
urls |
||||
}) |
||||
}, |
||||
// 获取图片的路径 |
||||
getSrc(item) { |
||||
return uni.$u.test.object(item) |
||||
? (this.keyName && item[this.keyName]) || item.src |
||||
: item |
||||
}, |
||||
// 单图时,获取图片的尺寸 |
||||
// 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸 |
||||
// 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent) |
||||
getImageRect() { |
||||
const src = this.getSrc(this.urls[0]) |
||||
uni.getImageInfo({ |
||||
src, |
||||
success: (res) => { |
||||
// 判断图片横向还是竖向展示方式 |
||||
const isHorizotal = res.width >= res.height |
||||
this.singleWidth = isHorizotal |
||||
? this.singleSize |
||||
: (res.width / res.height) * this.singleSize |
||||
this.singleHeight = !isHorizotal |
||||
? this.singleSize |
||||
: (res.height / res.width) * this.singleWidth |
||||
}, |
||||
fail: () => { |
||||
this.getComponentWidth() |
||||
} |
||||
}) |
||||
}, |
||||
// 获取组件的宽度 |
||||
async getComponentWidth() { |
||||
// 延时一定时间,以获取dom尺寸 |
||||
await uni.$u.sleep(30) |
||||
// #ifndef APP-NVUE |
||||
this.$uGetRect('.u-album__row').then((size) => { |
||||
this.singleWidth = size.width * this.singlePercent |
||||
}) |
||||
// #endif |
||||
|
||||
// #ifdef APP-NVUE |
||||
// 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组 |
||||
const ref = this.$refs['u-album__row'][0] |
||||
ref && |
||||
dom.getComponentRect(ref, (res) => { |
||||
this.singleWidth = res.size.width * this.singlePercent |
||||
}) |
||||
// #endif |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import '../../libs/css/components.scss'; |
||||
|
||||
.u-album { |
||||
@include flex(column); |
||||
|
||||
&__row { |
||||
@include flex(row); |
||||
flex-wrap: wrap; |
||||
|
||||
&__wrapper { |
||||
position: relative; |
||||
|
||||
&__text { |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
background-color: rgba(0, 0, 0, 0.3); |
||||
@include flex(row); |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,45 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 显示文字
|
||||
title: { |
||||
type: String, |
||||
default: defprops.alert.title |
||||
}, |
||||
// 主题,success/warning/info/error
|
||||
type: { |
||||
type: String, |
||||
default: defprops.alert.type |
||||
}, |
||||
// 辅助性文字
|
||||
description: { |
||||
type: String, |
||||
default: defprops.alert.description |
||||
}, |
||||
// 是否可关闭
|
||||
closable: { |
||||
type: Boolean, |
||||
default: defprops.alert.closable |
||||
}, |
||||
// 是否显示图标
|
||||
showIcon: { |
||||
type: Boolean, |
||||
default: defprops.alert.showIcon |
||||
}, |
||||
// 浅或深色调,light-浅色,dark-深色
|
||||
effect: { |
||||
type: String, |
||||
default: defprops.alert.effect |
||||
}, |
||||
// 文字是否居中
|
||||
center: { |
||||
type: Boolean, |
||||
default: defprops.alert.center |
||||
}, |
||||
// 字体大小
|
||||
fontSize: { |
||||
type: [String, Number], |
||||
default: defprops.alert.fontSize |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,245 @@ |
||||
<template> |
||||
<u-transition |
||||
mode="fade" |
||||
:show="show" |
||||
> |
||||
<view |
||||
class="u-alert" |
||||
:class="[`u-alert--${type}--${effect}`]" |
||||
@tap.stop="clickHandler" |
||||
:style="[$u.addStyle(customStyle)]" |
||||
> |
||||
<view |
||||
class="u-alert__icon" |
||||
v-if="showIcon" |
||||
> |
||||
<u-icon |
||||
:name="iconName" |
||||
size="18" |
||||
:color="iconColor" |
||||
></u-icon> |
||||
</view> |
||||
<view |
||||
class="u-alert__content" |
||||
:style="[{ |
||||
paddingRight: closable ? '20px' : 0 |
||||
}]" |
||||
> |
||||
<text |
||||
class="u-alert__content__title" |
||||
v-if="title" |
||||
:style="[{ |
||||
fontSize: $u.addUnit(fontSize), |
||||
textAlign: center ? 'center' : 'left' |
||||
}]" |
||||
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]" |
||||
>{{ title }}</text> |
||||
<text |
||||
class="u-alert__content__desc" |
||||
v-if="description" |
||||
:style="[{ |
||||
fontSize: $u.addUnit(fontSize), |
||||
textAlign: center ? 'center' : 'left' |
||||
}]" |
||||
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]" |
||||
>{{ description }}</text> |
||||
</view> |
||||
<view |
||||
class="u-alert__close" |
||||
v-if="closable" |
||||
@tap.stop="closeHandler" |
||||
> |
||||
<u-icon |
||||
name="close" |
||||
:color="iconColor" |
||||
size="15" |
||||
></u-icon> |
||||
</view> |
||||
</view> |
||||
</u-transition> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* Alert 警告提示 |
||||
* @description 警告提示,展现需要关注的信息。 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/alertTips.html |
||||
* |
||||
* @property {String} title 显示的文字 |
||||
* @property {String} type 使用预设的颜色 (默认 'warning' ) |
||||
* @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选 |
||||
* @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false ) |
||||
* @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false ) |
||||
* @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' ) |
||||
* @property {Boolean} center 文字是否居中 (默认 false ) |
||||
* @property {String | Number} fontSize 字体大小 (默认 14 ) |
||||
* @property {Object} customStyle 定义需要用到的外部样式 |
||||
* @event {Function} click 点击组件时触发 |
||||
* @example <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert> |
||||
*/ |
||||
export default { |
||||
name: 'u-alert', |
||||
mixins: [mpMixin, mixin, props], |
||||
data() { |
||||
return { |
||||
show: true |
||||
} |
||||
}, |
||||
computed: { |
||||
iconColor() { |
||||
return this.effect === 'light' ? this.type : '#fff' |
||||
}, |
||||
// 不同主题对应不同的图标 |
||||
iconName() { |
||||
switch (this.type) { |
||||
case 'success': |
||||
return 'checkmark-circle-fill'; |
||||
break; |
||||
case 'error': |
||||
return 'close-circle-fill'; |
||||
break; |
||||
case 'warning': |
||||
return 'error-circle-fill'; |
||||
break; |
||||
case 'info': |
||||
return 'info-circle-fill'; |
||||
break; |
||||
case 'primary': |
||||
return 'more-circle-fill'; |
||||
break; |
||||
default: |
||||
return 'error-circle-fill'; |
||||
} |
||||
} |
||||
}, |
||||
methods: { |
||||
// 点击内容 |
||||
clickHandler() { |
||||
this.$emit('click') |
||||
}, |
||||
// 点击关闭按钮 |
||||
closeHandler() { |
||||
this.show = false |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
.u-alert { |
||||
position: relative; |
||||
background-color: $u-primary; |
||||
padding: 8px 10px; |
||||
@include flex(row); |
||||
align-items: center; |
||||
border-top-left-radius: 4px; |
||||
border-top-right-radius: 4px; |
||||
border-bottom-left-radius: 4px; |
||||
border-bottom-right-radius: 4px; |
||||
|
||||
&--primary--dark { |
||||
background-color: $u-primary; |
||||
} |
||||
|
||||
&--primary--light { |
||||
background-color: #ecf5ff; |
||||
} |
||||
|
||||
&--error--dark { |
||||
background-color: $u-error; |
||||
} |
||||
|
||||
&--error--light { |
||||
background-color: #FEF0F0; |
||||
} |
||||
|
||||
&--success--dark { |
||||
background-color: $u-success; |
||||
} |
||||
|
||||
&--success--light { |
||||
background-color: #f5fff0; |
||||
} |
||||
|
||||
&--warning--dark { |
||||
background-color: $u-warning; |
||||
} |
||||
|
||||
&--warning--light { |
||||
background-color: #FDF6EC; |
||||
} |
||||
|
||||
&--info--dark { |
||||
background-color: $u-info; |
||||
} |
||||
|
||||
&--info--light { |
||||
background-color: #f4f4f5; |
||||
} |
||||
|
||||
&__icon { |
||||
margin-right: 5px; |
||||
} |
||||
|
||||
&__content { |
||||
@include flex(column); |
||||
flex: 1; |
||||
|
||||
&__title { |
||||
color: $u-main-color; |
||||
font-size: 14px; |
||||
font-weight: bold; |
||||
color: #fff; |
||||
margin-bottom: 2px; |
||||
} |
||||
|
||||
&__desc { |
||||
color: $u-main-color; |
||||
font-size: 14px; |
||||
flex-wrap: wrap; |
||||
color: #fff; |
||||
} |
||||
} |
||||
|
||||
&__title--dark, |
||||
&__desc--dark { |
||||
color: #FFFFFF; |
||||
} |
||||
|
||||
&__text--primary--light, |
||||
&__text--primary--light { |
||||
color: $u-primary; |
||||
} |
||||
|
||||
&__text--success--light, |
||||
&__text--success--light { |
||||
color: $u-success; |
||||
} |
||||
|
||||
&__text--warning--light, |
||||
&__text--warning--light { |
||||
color: $u-warning; |
||||
} |
||||
|
||||
&__text--error--light, |
||||
&__text--error--light { |
||||
color: $u-error; |
||||
} |
||||
|
||||
&__text--info--light, |
||||
&__text--info--light { |
||||
color: $u-info; |
||||
} |
||||
|
||||
&__close { |
||||
position: absolute; |
||||
top: 11px; |
||||
right: 10px; |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,53 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 头像图片组
|
||||
urls: { |
||||
type: Array, |
||||
default: defprops.avatarGroup.urls |
||||
}, |
||||
// 最多展示的头像数量
|
||||
maxCount: { |
||||
type: [String, Number], |
||||
default: defprops.avatarGroup.maxCount |
||||
}, |
||||
// 头像形状
|
||||
shape: { |
||||
type: String, |
||||
default: defprops.avatarGroup.shape |
||||
}, |
||||
// 图片裁剪模式
|
||||
mode: { |
||||
type: String, |
||||
default: defprops.avatarGroup.mode |
||||
}, |
||||
// 超出maxCount时是否显示查看更多的提示
|
||||
showMore: { |
||||
type: Boolean, |
||||
default: defprops.avatarGroup.showMore |
||||
}, |
||||
// 头像大小
|
||||
size: { |
||||
type: [String, Number], |
||||
default: defprops.avatarGroup.size |
||||
}, |
||||
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
keyName: { |
||||
type: String, |
||||
default: defprops.avatarGroup.keyName |
||||
}, |
||||
// 头像之间的遮挡比例
|
||||
gap: { |
||||
type: [String, Number], |
||||
validator(value) { |
||||
return value >= 0 && value <= 1 |
||||
}, |
||||
default: defprops.avatarGroup.gap |
||||
}, |
||||
// 需额外显示的值
|
||||
extraValue: { |
||||
type: [Number, String], |
||||
default: defprops.avatarGroup.extraValue |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,105 @@ |
||||
<template> |
||||
<view class="u-avatar-group"> |
||||
<view |
||||
class="u-avatar-group__item" |
||||
v-for="(item, index) in showUrl" |
||||
:key="index" |
||||
:style="{ |
||||
marginLeft: index === 0 ? 0 : $u.addUnit(-size * gap) |
||||
}" |
||||
> |
||||
<u-avatar |
||||
:size="size" |
||||
:shape="shape" |
||||
:mode="mode" |
||||
:src="$u.test.object(item) ? keyName && item[keyName] || item.url : item" |
||||
></u-avatar> |
||||
<view |
||||
class="u-avatar-group__item__show-more" |
||||
v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)" |
||||
@tap="clickHandler" |
||||
> |
||||
<u--text |
||||
color="#ffffff" |
||||
:size="size * 0.4" |
||||
:text="`+${extraValue || urls.length - showUrl.length}`" |
||||
align="center" |
||||
customStyle="justify-content: center" |
||||
></u--text> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* AvatarGroup 头像组 |
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/avatar.html |
||||
* |
||||
* @property {Array} urls 头像图片组 (默认 [] ) |
||||
* @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 ) |
||||
* @property {String} shape 头像形状( 'circle' (默认) | 'square' ) |
||||
* @property {String} mode 图片裁剪模式(默认 'scaleToFill' ) |
||||
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true ) |
||||
* @property {String | Number} size 头像大小 (默认 40 ) |
||||
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址 |
||||
* @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 ) |
||||
* @property {String | Number} extraValue 需额外显示的值 |
||||
* @event {Function} showMore 头像组更多点击 |
||||
* @example <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=> |
||||
*/ |
||||
export default { |
||||
name: 'u-avatar-group', |
||||
mixins: [mpMixin, mixin, props], |
||||
data() { |
||||
return { |
||||
|
||||
} |
||||
}, |
||||
computed: { |
||||
showUrl() { |
||||
return this.urls.slice(0, this.maxCount) |
||||
} |
||||
}, |
||||
methods: { |
||||
clickHandler() { |
||||
this.$emit('showMore') |
||||
} |
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
.u-avatar-group { |
||||
@include flex; |
||||
|
||||
&__item { |
||||
margin-left: -10px; |
||||
position: relative; |
||||
|
||||
&--no-indent { |
||||
// 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持 |
||||
margin-left: 0; |
||||
} |
||||
|
||||
&__show-more { |
||||
position: absolute; |
||||
top: 0; |
||||
bottom: 0; |
||||
left: 0; |
||||
right: 0; |
||||
background-color: rgba(0, 0, 0, 0.3); |
||||
@include flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
border-radius: 100px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,79 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 头像图片路径(不能为相对路径)
|
||||
src: { |
||||
type: String, |
||||
default: defprops.avatar.src |
||||
}, |
||||
// 头像形状,circle-圆形,square-方形
|
||||
shape: { |
||||
type: String, |
||||
default: defprops.avatar.shape |
||||
}, |
||||
// 头像尺寸
|
||||
size: { |
||||
type: [String, Number], |
||||
default: defprops.avatar.size |
||||
}, |
||||
// 裁剪模式
|
||||
mode: { |
||||
type: String, |
||||
default: defprops.avatar.mode |
||||
}, |
||||
// 显示的文字
|
||||
text: { |
||||
type: String, |
||||
default: defprops.avatar.text |
||||
}, |
||||
// 背景色
|
||||
bgColor: { |
||||
type: String, |
||||
default: defprops.avatar.bgColor |
||||
}, |
||||
// 文字颜色
|
||||
color: { |
||||
type: String, |
||||
default: defprops.avatar.color |
||||
}, |
||||
// 文字大小
|
||||
fontSize: { |
||||
type: [String, Number], |
||||
default: defprops.avatar.fontSize |
||||
}, |
||||
// 显示的图标
|
||||
icon: { |
||||
type: String, |
||||
default: defprops.avatar.icon |
||||
}, |
||||
// 显示小程序头像,只对百度,微信,QQ小程序有效
|
||||
mpAvatar: { |
||||
type: Boolean, |
||||
default: defprops.avatar.mpAvatar |
||||
}, |
||||
// 是否使用随机背景色
|
||||
randomBgColor: { |
||||
type: Boolean, |
||||
default: defprops.avatar.randomBgColor |
||||
}, |
||||
// 加载失败的默认头像(组件有内置默认图片)
|
||||
defaultUrl: { |
||||
type: String, |
||||
default: defprops.avatar.defaultUrl |
||||
}, |
||||
// 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
|
||||
colorIndex: { |
||||
type: [String, Number], |
||||
// 校验参数规则,索引在0-19之间
|
||||
validator(n) { |
||||
return uni.$u.test.range(n, [0, 19]) || n === '' |
||||
}, |
||||
default: defprops.avatar.colorIndex |
||||
}, |
||||
// 组件标识符
|
||||
name: { |
||||
type: String, |
||||
default: defprops.avatar.name |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,174 @@ |
||||
<template> |
||||
<view |
||||
class="u-avatar" |
||||
:class="[`u-avatar--${shape}`]" |
||||
:style="[{ |
||||
backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : $u.random(0, 19)] : bgColor) : 'transparent', |
||||
width: $u.addUnit(size), |
||||
height: $u.addUnit(size), |
||||
}, $u.addStyle(customStyle)]" |
||||
@tap="clickHandler" |
||||
> |
||||
<slot> |
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU --> |
||||
<open-data |
||||
v-if="mpAvatar && allowMp" |
||||
type="userAvatarUrl" |
||||
:style="[{ |
||||
width: $u.addUnit(size), |
||||
height: $u.addUnit(size) |
||||
}]" |
||||
/> |
||||
<!-- #endif --> |
||||
<!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU --> |
||||
<template v-if="mpAvatar && allowMp"></template> |
||||
<!-- #endif --> |
||||
<u-icon |
||||
v-else-if="icon" |
||||
:name="icon" |
||||
:size="fontSize" |
||||
:color="color" |
||||
></u-icon> |
||||
<u--text |
||||
v-else-if="text" |
||||
:text="text" |
||||
:size="fontSize" |
||||
:color="color" |
||||
align="center" |
||||
customStyle="justify-content: center" |
||||
></u--text> |
||||
<image |
||||
class="u-avatar__image" |
||||
v-else |
||||
:class="[`u-avatar__image--${shape}`]" |
||||
:src="avatarUrl || defaultUrl" |
||||
:mode="mode" |
||||
@error="errorHandler" |
||||
:style="[{ |
||||
width: $u.addUnit(size), |
||||
height: $u.addUnit(size) |
||||
}]" |
||||
></image> |
||||
</slot> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
const base64Avatar = |
||||
"data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z"; |
||||
/** |
||||
* Avatar 头像 |
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/avatar.html |
||||
* |
||||
* @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径) |
||||
* @property {String} shape 头像形状 ( circle (默认) | square) |
||||
* @property {String | Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值 (默认 40 ) |
||||
* @property {String} mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值 (默认 'scaleToFill' ) |
||||
* @property {String} text 用文字替代图片,级别优先于src |
||||
* @property {String} bgColor 背景颜色,一般显示文字时用 (默认 '#c0c4cc' ) |
||||
* @property {String} color 文字颜色 (默认 '#ffffff' ) |
||||
* @property {String | Number} fontSize 文字大小 (默认 18 ) |
||||
* @property {String} icon 显示的图标 |
||||
* @property {Boolean} mpAvatar 显示小程序头像,只对百度,微信,QQ小程序有效 (默认 false ) |
||||
* @property {Boolean} randomBgColor 是否使用随机背景色 (默认 false ) |
||||
* @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片) |
||||
* @property {String | Number} colorIndex 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间 |
||||
* @property {String} name 组件标识符 (默认 'level' ) |
||||
* @property {Object} customStyle 定义需要用到的外部样式 |
||||
* |
||||
* @event {Function} click 点击组件时触发 index: 用户传递的标识符 |
||||
* @example <u-avatar :src="src" mode="square"></u-avatar> |
||||
*/ |
||||
export default { |
||||
name: 'u-avatar', |
||||
mixins: [mpMixin, mixin, props], |
||||
data() { |
||||
return { |
||||
// 如果配置randomBgColor参数为true,在图标或者文字的模式下,会随机从中取出一个颜色值当做背景色 |
||||
colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2', |
||||
'#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee', |
||||
'#73d1f1', |
||||
'#80a7dc' |
||||
], |
||||
avatarUrl: this.src, |
||||
allowMp: false |
||||
} |
||||
}, |
||||
watch: { |
||||
// 监听头像src的变化,赋值给内部的avatarUrl变量,因为图片加载失败时,需要修改图片的src为默认值 |
||||
// 而组件内部不能直接修改props的值,所以需要一个中间变量 |
||||
src: { |
||||
immediate: true, |
||||
handler(newVal) { |
||||
this.avatarUrl = newVal |
||||
// 如果没有传src,则主动触发error事件,用于显示默认的头像,否则src为''空字符等的时候,会无内容展示 |
||||
if(!newVal) { |
||||
this.errorHandler() |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
computed: { |
||||
imageStyle() { |
||||
const style = {} |
||||
return style |
||||
} |
||||
}, |
||||
created() { |
||||
this.init() |
||||
}, |
||||
methods: { |
||||
init() { |
||||
// 目前只有这几个小程序平台具有open-data标签 |
||||
// 其他平台可以通过uni.getUserInfo类似接口获取信息,但是需要弹窗授权(首次),不合符组件逻辑 |
||||
// 故目前自动获取小程序头像只支持这几个平台 |
||||
// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU |
||||
this.allowMp = true |
||||
// #endif |
||||
}, |
||||
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式 |
||||
isImg() { |
||||
return this.src.indexOf('/') !== -1 |
||||
}, |
||||
// 图片加载时失败时触发 |
||||
errorHandler() { |
||||
this.avatarUrl = this.defaultUrl || base64Avatar |
||||
}, |
||||
clickHandler() { |
||||
this.$emit('click', this.name) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
.u-avatar { |
||||
@include flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
|
||||
&--circle { |
||||
border-radius: 100px; |
||||
} |
||||
|
||||
&--square { |
||||
border-radius: 4px; |
||||
} |
||||
|
||||
&__image { |
||||
&--circle { |
||||
border-radius: 100px; |
||||
} |
||||
|
||||
&--square { |
||||
border-radius: 4px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,55 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 返回顶部的形状,circle-圆形,square-方形
|
||||
mode: { |
||||
type: String, |
||||
default: defprops.backtop.mode |
||||
}, |
||||
// 自定义图标
|
||||
icon: { |
||||
type: String, |
||||
default: defprops.backtop.icon |
||||
}, |
||||
// 提示文字
|
||||
text: { |
||||
type: String, |
||||
default: defprops.backtop.text |
||||
}, |
||||
// 返回顶部滚动时间
|
||||
duration: { |
||||
type: [String, Number], |
||||
default: defprops.backtop.duration |
||||
}, |
||||
// 滚动距离
|
||||
scrollTop: { |
||||
type: [String, Number], |
||||
default: defprops.backtop.scrollTop |
||||
}, |
||||
// 距离顶部多少距离显示,单位px
|
||||
top: { |
||||
type: [String, Number], |
||||
default: defprops.backtop.top |
||||
}, |
||||
// 返回顶部按钮到底部的距离,单位px
|
||||
bottom: { |
||||
type: [String, Number], |
||||
default: defprops.backtop.bottom |
||||
}, |
||||
// 返回顶部按钮到右边的距离,单位px
|
||||
right: { |
||||
type: [String, Number], |
||||
default: defprops.backtop.right |
||||
}, |
||||
// 层级
|
||||
zIndex: { |
||||
type: [String, Number], |
||||
default: defprops.backtop.zIndex |
||||
}, |
||||
// 图标的样式,对象形式
|
||||
iconStyle: { |
||||
type: Object, |
||||
default: defprops.backtop.iconStyle |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,131 @@ |
||||
<template> |
||||
<u-transition |
||||
mode="fade" |
||||
:customStyle="backTopStyle" |
||||
:show="show" |
||||
> |
||||
<view |
||||
class="u-back-top" |
||||
:style="[contentStyle]" |
||||
v-if="!$slots.default && !$slots.$default" |
||||
@click="backToTop" |
||||
> |
||||
<u-icon |
||||
:name="icon" |
||||
:custom-style="iconStyle" |
||||
></u-icon> |
||||
<text |
||||
v-if="text" |
||||
class="u-back-top__text" |
||||
>{{text}}</text> |
||||
</view> |
||||
<slot v-else /> |
||||
</u-transition> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
// #ifdef APP-NVUE |
||||
const dom = weex.requireModule('dom') |
||||
// #endif |
||||
/** |
||||
* backTop 返回顶部 |
||||
* @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。 |
||||
* @tutorial https://uviewui.com/components/backTop.html |
||||
* |
||||
* @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' ) |
||||
* @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例 |
||||
* @property {String} text 提示文字 |
||||
* @property {String | Number} duration 返回顶部滚动时间 (默认 100) |
||||
* @property {String | Number} scrollTop 滚动距离 (默认 0 ) |
||||
* @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 ) |
||||
* @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 ) |
||||
* @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 ) |
||||
* @property {String | Number} zIndex 层级 (默认 9 ) |
||||
* @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'}) |
||||
* @property {Object} customStyle 定义需要用到的外部样式 |
||||
* |
||||
* @example <u-back-top :scrollTop="scrollTop"></u-back-top> |
||||
*/ |
||||
export default { |
||||
name: 'u-back-top', |
||||
mixins: [mpMixin, mixin,props], |
||||
computed: { |
||||
backTopStyle() { |
||||
// 动画组件样式 |
||||
const style = { |
||||
bottom: uni.$u.addUnit(this.bottom), |
||||
right: uni.$u.addUnit(this.right), |
||||
width: '40px', |
||||
height: '40px', |
||||
position: 'fixed', |
||||
zIndex: 10, |
||||
} |
||||
return style |
||||
}, |
||||
show() { |
||||
return uni.$u.getPx(this.scrollTop) > uni.$u.getPx(this.top) |
||||
}, |
||||
contentStyle() { |
||||
const style = {} |
||||
let radius = 0 |
||||
// 是否圆形 |
||||
if(this.mode === 'circle') { |
||||
radius = '100px' |
||||
} else { |
||||
radius = '4px' |
||||
} |
||||
// 为了兼容安卓nvue,只能这么分开写 |
||||
style.borderTopLeftRadius = radius |
||||
style.borderTopRightRadius = radius |
||||
style.borderBottomLeftRadius = radius |
||||
style.borderBottomRightRadius = radius |
||||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) |
||||
} |
||||
}, |
||||
methods: { |
||||
backToTop() { |
||||
// #ifdef APP-NVUE |
||||
if (!this.$parent.$refs['u-back-top']) { |
||||
uni.$u.error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`) |
||||
} |
||||
dom.scrollToElement(this.$parent.$refs['u-back-top'], { |
||||
offset: 0 |
||||
}) |
||||
// #endif |
||||
|
||||
// #ifndef APP-NVUE |
||||
uni.pageScrollTo({ |
||||
scrollTop: 0, |
||||
duration: this.duration |
||||
}); |
||||
// #endif |
||||
this.$emit('click') |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import '../../libs/css/components.scss'; |
||||
$u-back-top-flex:1 !default; |
||||
$u-back-top-height:100% !default; |
||||
$u-back-top-background-color:#E1E1E1 !default; |
||||
$u-back-top-tips-font-size:12px !default; |
||||
.u-back-top { |
||||
@include flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
flex:$u-back-top-flex; |
||||
height: $u-back-top-height; |
||||
justify-content: center; |
||||
background-color: $u-back-top-background-color; |
||||
|
||||
&__tips { |
||||
font-size:$u-back-top-tips-font-size; |
||||
transform: scale(0.8); |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,78 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 是否显示圆点
|
||||
isDot: { |
||||
type: Boolean, |
||||
default: defprops.badge.isDot |
||||
}, |
||||
// 显示的内容
|
||||
value: { |
||||
type: [Number, String], |
||||
default: defprops.badge.value |
||||
}, |
||||
// 显示的内容
|
||||
modelValue: { |
||||
type: [Number, String], |
||||
default: defprops.badge.modelValue |
||||
}, |
||||
// 是否显示
|
||||
show: { |
||||
type: Boolean, |
||||
default: defprops.badge.show |
||||
}, |
||||
// 最大值,超过最大值会显示 '{max}+'
|
||||
max: { |
||||
type: [Number, String], |
||||
default: defprops.badge.max |
||||
}, |
||||
// 主题类型,error|warning|success|primary
|
||||
type: { |
||||
type: String, |
||||
default: defprops.badge.type |
||||
}, |
||||
// 当数值为 0 时,是否展示 Badge
|
||||
showZero: { |
||||
type: Boolean, |
||||
default: defprops.badge.showZero |
||||
}, |
||||
// 背景颜色,优先级比type高,如设置,type参数会失效
|
||||
bgColor: { |
||||
type: [String, null], |
||||
default: defprops.badge.bgColor |
||||
}, |
||||
// 字体颜色
|
||||
color: { |
||||
type: [String, null], |
||||
default: defprops.badge.color |
||||
}, |
||||
// 徽标形状,circle-四角均为圆角,horn-左下角为直角
|
||||
shape: { |
||||
type: String, |
||||
default: defprops.badge.shape |
||||
}, |
||||
// 设置数字的显示方式,overflow|ellipsis|limit
|
||||
// overflow会根据max字段判断,超出显示`${max}+`
|
||||
// ellipsis会根据max判断,超出显示`${max}...`
|
||||
// limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
|
||||
numberType: { |
||||
type: String, |
||||
default: defprops.badge.numberType |
||||
}, |
||||
// 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
|
||||
offset: { |
||||
type: Array, |
||||
default: defprops.badge.offset |
||||
}, |
||||
// 是否反转背景和字体颜色
|
||||
inverted: { |
||||
type: Boolean, |
||||
default: defprops.badge.inverted |
||||
}, |
||||
// 是否绝对定位
|
||||
absolute: { |
||||
type: Boolean, |
||||
default: defprops.badge.absolute |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,173 @@ |
||||
<template> |
||||
<text |
||||
v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)" |
||||
:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]" |
||||
:style="[$u.addStyle(customStyle), badgeStyle]" |
||||
class="u-badge" |
||||
>{{ isDot ? '' :showValue }}</text> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* badge 徽标数 |
||||
* @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。 |
||||
* @tutorial https://uviewui.com/components/badge.html |
||||
* |
||||
* @property {Boolean} isDot 是否显示圆点 (默认 false ) |
||||
* @property {String | Number} value 显示的内容 |
||||
* @property {Boolean} show 是否显示 (默认 true ) |
||||
* @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999) |
||||
* @property {String} type 主题类型,error|warning|success|primary (默认 'error' ) |
||||
* @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false ) |
||||
* @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效 |
||||
* @property {String} color 字体颜色 (默认 '#ffffff' ) |
||||
* @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' ) |
||||
* @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' ) |
||||
* @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效 |
||||
* @property {Boolean} inverted 是否反转背景和字体颜色(默认 false ) |
||||
* @property {Boolean} absolute 是否绝对定位(默认 false ) |
||||
* @property {Object} customStyle 定义需要用到的外部样式 |
||||
* @example <u-badge :type="type" :count="count"></u-badge> |
||||
*/ |
||||
export default { |
||||
name: 'u-badge', |
||||
mixins: [mpMixin, props, mixin], |
||||
computed: { |
||||
// 是否将badge中心与父组件右上角重合 |
||||
boxStyle() { |
||||
let style = {}; |
||||
return style; |
||||
}, |
||||
// 整个组件的样式 |
||||
badgeStyle() { |
||||
const style = {} |
||||
if(this.color) { |
||||
style.color = this.color |
||||
} |
||||
if (this.bgColor && !this.inverted) { |
||||
style.backgroundColor = this.bgColor |
||||
} |
||||
if (this.absolute) { |
||||
style.position = 'absolute' |
||||
// 如果有设置offset参数 |
||||
if(this.offset.length) { |
||||
// top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top |
||||
const top = this.offset[0] |
||||
const right = this.offset[1] || top |
||||
style.top = uni.$u.addUnit(top) |
||||
style.right = uni.$u.addUnit(right) |
||||
} |
||||
} |
||||
return style |
||||
}, |
||||
showValue() { |
||||
switch (this.numberType) { |
||||
case "overflow": |
||||
return Number(this.value) > Number(this.max) ? this.max + "+" : this.value |
||||
break; |
||||
case "ellipsis": |
||||
return Number(this.value) > Number(this.max) ? "..." : this.value |
||||
break; |
||||
case "limit": |
||||
return Number(this.value) > 999 ? Number(this.value) >= 9999 ? |
||||
Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value / |
||||
1e3 * 100) / 100 + "k" : this.value |
||||
break; |
||||
default: |
||||
return Number(this.value) |
||||
} |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
$u-badge-primary: $u-primary !default; |
||||
$u-badge-error: $u-error !default; |
||||
$u-badge-success: $u-success !default; |
||||
$u-badge-info: $u-info !default; |
||||
$u-badge-warning: $u-warning !default; |
||||
$u-badge-dot-radius: 100px !default; |
||||
$u-badge-dot-size: 8px !default; |
||||
$u-badge-dot-right: 4px !default; |
||||
$u-badge-dot-top: 0 !default; |
||||
$u-badge-text-font-size: 11px !default; |
||||
$u-badge-text-right: 10px !default; |
||||
$u-badge-text-padding: 2px 5px !default; |
||||
$u-badge-text-align: center !default; |
||||
$u-badge-text-color: #FFFFFF !default; |
||||
|
||||
.u-badge { |
||||
border-top-right-radius: $u-badge-dot-radius; |
||||
border-top-left-radius: $u-badge-dot-radius; |
||||
border-bottom-left-radius: $u-badge-dot-radius; |
||||
border-bottom-right-radius: $u-badge-dot-radius; |
||||
@include flex; |
||||
line-height: $u-badge-text-font-size; |
||||
text-align: $u-badge-text-align; |
||||
font-size: $u-badge-text-font-size; |
||||
color: $u-badge-text-color; |
||||
|
||||
&--dot { |
||||
height: $u-badge-dot-size; |
||||
width: $u-badge-dot-size; |
||||
} |
||||
|
||||
&--inverted { |
||||
font-size: 13px; |
||||
} |
||||
|
||||
&--not-dot { |
||||
padding: $u-badge-text-padding; |
||||
} |
||||
|
||||
&--horn { |
||||
border-bottom-left-radius: 0; |
||||
} |
||||
|
||||
&--primary { |
||||
background-color: $u-badge-primary; |
||||
} |
||||
|
||||
&--primary--inverted { |
||||
color: $u-badge-primary; |
||||
} |
||||
|
||||
&--error { |
||||
background-color: $u-badge-error; |
||||
} |
||||
|
||||
&--error--inverted { |
||||
color: $u-badge-error; |
||||
} |
||||
|
||||
&--success { |
||||
background-color: $u-badge-success; |
||||
} |
||||
|
||||
&--success--inverted { |
||||
color: $u-badge-success; |
||||
} |
||||
|
||||
&--info { |
||||
background-color: $u-badge-info; |
||||
} |
||||
|
||||
&--info--inverted { |
||||
color: $u-badge-info; |
||||
} |
||||
|
||||
&--warning { |
||||
background-color: $u-badge-warning; |
||||
} |
||||
|
||||
&--warning--inverted { |
||||
color: $u-badge-warning; |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,46 @@ |
||||
$u-button-active-opacity:0.75 !default; |
||||
$u-button-loading-text-margin-left:4px !default; |
||||
$u-button-text-color: #FFFFFF !default; |
||||
$u-button-text-plain-error-color:$u-error !default; |
||||
$u-button-text-plain-warning-color:$u-warning !default; |
||||
$u-button-text-plain-success-color:$u-success !default; |
||||
$u-button-text-plain-info-color:$u-info !default; |
||||
$u-button-text-plain-primary-color:$u-primary !default; |
||||
.u-button { |
||||
&--active { |
||||
opacity: $u-button-active-opacity; |
||||
} |
||||
|
||||
&--active--plain { |
||||
background-color: rgb(217, 217, 217); |
||||
} |
||||
|
||||
&__loading-text { |
||||
margin-left:$u-button-loading-text-margin-left; |
||||
} |
||||
|
||||
&__text, |
||||
&__loading-text { |
||||
color:$u-button-text-color; |
||||
} |
||||
|
||||
&__text--plain--error { |
||||
color:$u-button-text-plain-error-color; |
||||
} |
||||
|
||||
&__text--plain--warning { |
||||
color:$u-button-text-plain-warning-color; |
||||
} |
||||
|
||||
&__text--plain--success{ |
||||
color:$u-button-text-plain-success-color; |
||||
} |
||||
|
||||
&__text--plain--info { |
||||
color:$u-button-text-plain-info-color; |
||||
} |
||||
|
||||
&__text--plain--primary { |
||||
color:$u-button-text-plain-primary-color; |
||||
} |
||||
} |
||||
@ -0,0 +1,162 @@ |
||||
/* |
||||
* @Author : LQ |
||||
* @Description : |
||||
* @version : 1.0 |
||||
* @Date : 2021-08-16 10:04:04 |
||||
* @LastAuthor : LQ |
||||
* @lastTime : 2021-08-16 10:04:24 |
||||
* @FilePath : /u-view2.0/uview-ui/components/u-button/props.js |
||||
*/ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 是否细边框
|
||||
hairline: { |
||||
type: Boolean, |
||||
default: defprops.button.hairline |
||||
}, |
||||
// 按钮的预置样式,info,primary,error,warning,success
|
||||
type: { |
||||
type: String, |
||||
default: defprops.button.type |
||||
}, |
||||
// 按钮尺寸,large,normal,small,mini
|
||||
size: { |
||||
type: String, |
||||
default: defprops.button.size |
||||
}, |
||||
// 按钮形状,circle(两边为半圆),square(带圆角)
|
||||
shape: { |
||||
type: String, |
||||
default: defprops.button.shape |
||||
}, |
||||
// 按钮是否镂空
|
||||
plain: { |
||||
type: Boolean, |
||||
default: defprops.button.plain |
||||
}, |
||||
// 是否禁止状态
|
||||
disabled: { |
||||
type: Boolean, |
||||
default: defprops.button.disabled |
||||
}, |
||||
// 是否加载中
|
||||
loading: { |
||||
type: Boolean, |
||||
default: defprops.button.loading |
||||
}, |
||||
// 加载中提示文字
|
||||
loadingText: { |
||||
type: [String, Number], |
||||
default: defprops.button.loadingText |
||||
}, |
||||
// 加载状态图标类型
|
||||
loadingMode: { |
||||
type: String, |
||||
default: defprops.button.loadingMode |
||||
}, |
||||
// 加载图标大小
|
||||
loadingSize: { |
||||
type: [String, Number], |
||||
default: defprops.button.loadingSize |
||||
}, |
||||
// 开放能力,具体请看uniapp稳定关于button组件部分说明
|
||||
// https://uniapp.dcloud.io/component/button
|
||||
openType: { |
||||
type: String, |
||||
default: defprops.button.openType |
||||
}, |
||||
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
// 取值为submit(提交表单),reset(重置表单)
|
||||
formType: { |
||||
type: String, |
||||
default: defprops.button.formType |
||||
}, |
||||
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
|
||||
// 只微信小程序、QQ小程序有效
|
||||
appParameter: { |
||||
type: String, |
||||
default: defprops.button.appParameter |
||||
}, |
||||
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
|
||||
hoverStopPropagation: { |
||||
type: Boolean, |
||||
default: defprops.button.hoverStopPropagation |
||||
}, |
||||
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
|
||||
lang: { |
||||
type: String, |
||||
default: defprops.button.lang |
||||
}, |
||||
// 会话来源,open-type="contact"时有效。只微信小程序有效
|
||||
sessionFrom: { |
||||
type: String, |
||||
default: defprops.button.sessionFrom |
||||
}, |
||||
// 会话内消息卡片标题,open-type="contact"时有效
|
||||
// 默认当前标题,只微信小程序有效
|
||||
sendMessageTitle: { |
||||
type: String, |
||||
default: defprops.button.sendMessageTitle |
||||
}, |
||||
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
|
||||
// 默认当前分享路径,只微信小程序有效
|
||||
sendMessagePath: { |
||||
type: String, |
||||
default: defprops.button.sendMessagePath |
||||
}, |
||||
// 会话内消息卡片图片,open-type="contact"时有效
|
||||
// 默认当前页面截图,只微信小程序有效
|
||||
sendMessageImg: { |
||||
type: String, |
||||
default: defprops.button.sendMessageImg |
||||
}, |
||||
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
|
||||
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
|
||||
showMessageCard: { |
||||
type: Boolean, |
||||
default: defprops.button.showMessageCard |
||||
}, |
||||
// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
dataName: { |
||||
type: String, |
||||
default: defprops.button.dataName |
||||
}, |
||||
// 节流,一定时间内只能触发一次
|
||||
throttleTime: { |
||||
type: [String, Number], |
||||
default: defprops.button.throttleTime |
||||
}, |
||||
// 按住后多久出现点击态,单位毫秒
|
||||
hoverStartTime: { |
||||
type: [String, Number], |
||||
default: defprops.button.hoverStartTime |
||||
}, |
||||
// 手指松开后点击态保留时间,单位毫秒
|
||||
hoverStayTime: { |
||||
type: [String, Number], |
||||
default: defprops.button.hoverStayTime |
||||
}, |
||||
// 按钮文字,之所以通过props传入,是因为slot传入的话
|
||||
// nvue中无法控制文字的样式
|
||||
text: { |
||||
type: [String, Number], |
||||
default: defprops.button.text |
||||
}, |
||||
// 按钮图标
|
||||
icon: { |
||||
type: String, |
||||
default: defprops.button.icon |
||||
}, |
||||
// 按钮图标
|
||||
iconColor: { |
||||
type: String, |
||||
default: defprops.button.icon |
||||
}, |
||||
// 按钮颜色,支持传入linear-gradient渐变色
|
||||
color: { |
||||
type: String, |
||||
default: defprops.button.color |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,494 @@ |
||||
<template> |
||||
<!-- #ifndef APP-NVUE --> |
||||
<button |
||||
:hover-start-time="Number(hoverStartTime)" |
||||
:hover-stay-time="Number(hoverStayTime)" |
||||
:form-type="formType" |
||||
:open-type="openType" |
||||
:app-parameter="appParameter" |
||||
:hover-stop-propagation="hoverStopPropagation" |
||||
:send-message-title="sendMessageTitle" |
||||
:send-message-path="sendMessagePath" |
||||
:lang="lang" |
||||
:data-name="dataName" |
||||
:session-from="sessionFrom" |
||||
:send-message-img="sendMessageImg" |
||||
:show-message-card="showMessageCard" |
||||
@getphonenumber="getphonenumber" |
||||
@getuserinfo="getuserinfo" |
||||
@error="error" |
||||
@opensetting="opensetting" |
||||
@launchapp="launchapp" |
||||
:hover-class="!disabled && !loading ? 'u-button--active' : ''" |
||||
class="u-button u-reset-button" |
||||
:style="[baseColor, $u.addStyle(customStyle)]" |
||||
@tap="clickHandler" |
||||
:class="bemClass" |
||||
> |
||||
<template v-if="loading"> |
||||
<u-loading-icon |
||||
:mode="loadingMode" |
||||
:size="loadingSize * 1.15" |
||||
:color="loadingColor" |
||||
></u-loading-icon> |
||||
<text |
||||
class="u-button__loading-text" |
||||
:style="[{ fontSize: textSize + 'px' }]" |
||||
>{{ loadingText || text }}</text |
||||
> |
||||
</template> |
||||
<template v-else> |
||||
<u-icon |
||||
v-if="icon" |
||||
:name="icon" |
||||
:color="iconColorCom" |
||||
:size="textSize * 1.35" |
||||
:customStyle="{ marginRight: '2px' }" |
||||
></u-icon> |
||||
<slot> |
||||
<text |
||||
class="u-button__text" |
||||
:style="[{ fontSize: textSize + 'px' }]" |
||||
>{{ text }}</text |
||||
> |
||||
</slot> |
||||
</template> |
||||
</button> |
||||
<!-- #endif --> |
||||
|
||||
<!-- #ifdef APP-NVUE --> |
||||
<view |
||||
:hover-start-time="Number(hoverStartTime)" |
||||
:hover-stay-time="Number(hoverStayTime)" |
||||
class="u-button" |
||||
:hover-class=" |
||||
!disabled && !loading && !color && (plain || type === 'info') |
||||
? 'u-button--active--plain' |
||||
: !disabled && !loading && !plain |
||||
? 'u-button--active' |
||||
: '' |
||||
" |
||||
@tap="clickHandler" |
||||
:class="bemClass" |
||||
:style="[baseColor, $u.addStyle(customStyle)]" |
||||
> |
||||
<template v-if="loading"> |
||||
<u-loading-icon |
||||
:mode="loadingMode" |
||||
:size="loadingSize * 1.15" |
||||
:color="loadingColor" |
||||
></u-loading-icon> |
||||
<text |
||||
class="u-button__loading-text" |
||||
:style="[nvueTextStyle]" |
||||
:class="[plain && `u-button__text--plain--${type}`]" |
||||
>{{ loadingText || text }}</text |
||||
> |
||||
</template> |
||||
<template v-else> |
||||
<u-icon |
||||
v-if="icon" |
||||
:name="icon" |
||||
:color="iconColorCom" |
||||
:size="textSize * 1.35" |
||||
></u-icon> |
||||
<text |
||||
class="u-button__text" |
||||
:style="[ |
||||
{ |
||||
marginLeft: icon ? '2px' : 0, |
||||
}, |
||||
nvueTextStyle, |
||||
]" |
||||
:class="[plain && `u-button__text--plain--${type}`]" |
||||
>{{ text }}</text |
||||
> |
||||
</template> |
||||
</view> |
||||
<!-- #endif --> |
||||
</template> |
||||
|
||||
<script> |
||||
import button from "../../libs/mixin/button.js"; |
||||
import openType from "../../libs/mixin/openType.js"; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
import props from "./props.js"; |
||||
/** |
||||
* button 按钮 |
||||
* @description Button 按钮 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/button.html |
||||
* |
||||
* @property {Boolean} hairline 是否显示按钮的细边框 (默认 true ) |
||||
* @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' ) |
||||
* @property {String} size 按钮尺寸,large,normal,mini (默认 normal) |
||||
* @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' ) |
||||
* @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false) |
||||
* @property {Boolean} disabled 是否禁用 (默认 false) |
||||
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false) |
||||
* @property {String | Number} loadingText 加载中提示文字 |
||||
* @property {String} loadingMode 加载状态图标类型 (默认 'spinner' ) |
||||
* @property {String | Number} loadingSize 加载图标大小 (默认 15 ) |
||||
* @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明 |
||||
* @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 |
||||
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效) |
||||
* @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true ) |
||||
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en ) |
||||
* @property {String} sessionFrom 会话来源,openType="contact"时有效 |
||||
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效 |
||||
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效 |
||||
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效 |
||||
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false) |
||||
* @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 |
||||
* @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 ) |
||||
* @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 ) |
||||
* @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 ) |
||||
* @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式) |
||||
* @property {String} icon 按钮图标 |
||||
* @property {String} iconColor 按钮图标颜色 |
||||
* @property {String} color 按钮颜色,支持传入linear-gradient渐变色 |
||||
* @property {Object} customStyle 定义需要用到的外部样式 |
||||
* |
||||
* @event {Function} click 非禁止并且非加载中,才能点击 |
||||
* @event {Function} getphonenumber open-type="getPhoneNumber"时有效 |
||||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo |
||||
* @event {Function} error 当使用开放能力时,发生错误的回调 |
||||
* @event {Function} opensetting 在打开授权设置页并关闭后回调 |
||||
* @event {Function} launchapp 打开 APP 成功的回调 |
||||
* @example <u-button>月落</u-button> |
||||
*/ |
||||
export default { |
||||
name: "u-button", |
||||
// #ifdef MP |
||||
mixins: [mpMixin, mixin, button, openType, props], |
||||
// #endif |
||||
// #ifndef MP |
||||
mixins: [mpMixin, mixin, props], |
||||
// #endif |
||||
data() { |
||||
return {}; |
||||
}, |
||||
computed: { |
||||
// 生成bem风格的类名 |
||||
bemClass() { |
||||
// this.bem为一个computed变量,在mixin中 |
||||
if (!this.color) { |
||||
return this.bem( |
||||
"button", |
||||
["type", "shape", "size"], |
||||
["disabled", "plain", "hairline"] |
||||
); |
||||
} else { |
||||
// 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式 |
||||
return this.bem( |
||||
"button", |
||||
["shape", "size"], |
||||
["disabled", "plain", "hairline"] |
||||
); |
||||
} |
||||
}, |
||||
loadingColor() { |
||||
if (this.plain) { |
||||
// 如果有设置color值,则用color值,否则使用type主题颜色 |
||||
return this.color |
||||
? this.color |
||||
: uni.$u.config.color[`u-${this.type}`]; |
||||
} |
||||
if (this.type === "info") { |
||||
return "#c9c9c9"; |
||||
} |
||||
return "rgb(200, 200, 200)"; |
||||
}, |
||||
iconColorCom() { |
||||
// 如果是镂空状态,设置了color就用color值,否则使用主题颜色, |
||||
// u-icon的color能接受一个主题颜色的值 |
||||
if (this.iconColor) return this.iconColor; |
||||
if (this.plain) { |
||||
return this.color ? this.color : this.type; |
||||
} else { |
||||
return this.type === "info" ? "#000000" : "#ffffff"; |
||||
} |
||||
}, |
||||
baseColor() { |
||||
let style = {}; |
||||
if (this.color) { |
||||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色 |
||||
style.color = this.plain ? this.color : "white"; |
||||
if (!this.plain) { |
||||
// 非镂空,背景色使用自定义的颜色 |
||||
style["background-color"] = this.color; |
||||
} |
||||
if (this.color.indexOf("gradient") !== -1) { |
||||
// 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色 |
||||
// weex文档说明可以写borderWidth的形式,为什么这里需要分开写? |
||||
// 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效 |
||||
style.borderTopWidth = 0; |
||||
style.borderRightWidth = 0; |
||||
style.borderBottomWidth = 0; |
||||
style.borderLeftWidth = 0; |
||||
if (!this.plain) { |
||||
style.backgroundImage = this.color; |
||||
} |
||||
} else { |
||||
// 非渐变色,则设置边框相关的属性 |
||||
style.borderColor = this.color; |
||||
style.borderWidth = "1px"; |
||||
style.borderStyle = "solid"; |
||||
} |
||||
} |
||||
return style; |
||||
}, |
||||
// nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置 |
||||
nvueTextStyle() { |
||||
let style = {}; |
||||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色 |
||||
if (this.type === "info") { |
||||
style.color = "#323233"; |
||||
} |
||||
if (this.color) { |
||||
style.color = this.plain ? this.color : "white"; |
||||
} |
||||
style.fontSize = this.textSize + "px"; |
||||
return style; |
||||
}, |
||||
// 字体大小 |
||||
textSize() { |
||||
let fontSize = 14, |
||||
{ size } = this; |
||||
if (size === "large") fontSize = 16; |
||||
if (size === "normal") fontSize = 14; |
||||
if (size === "small") fontSize = 12; |
||||
if (size === "mini") fontSize = 10; |
||||
return fontSize; |
||||
}, |
||||
}, |
||||
emits: ['click', 'getphonenumber', 'getuserinfo', |
||||
'error', 'opensetting', 'launchapp'], |
||||
methods: { |
||||
clickHandler() { |
||||
// 非禁止并且非加载中,才能点击 |
||||
if (!this.disabled && !this.loading) { |
||||
// 进行节流控制,每this.throttle毫秒内,只在开始处执行 |
||||
uni.$u.throttle(() => { |
||||
this.$emit("click"); |
||||
}, this.throttleTime); |
||||
} |
||||
}, |
||||
// 下面为对接uniapp官方按钮开放能力事件回调的对接 |
||||
getphonenumber(res) { |
||||
this.$emit("getphonenumber", res); |
||||
}, |
||||
getuserinfo(res) { |
||||
this.$emit("getuserinfo", res); |
||||
}, |
||||
error(res) { |
||||
this.$emit("error", res); |
||||
}, |
||||
opensetting(res) { |
||||
this.$emit("opensetting", res); |
||||
}, |
||||
launchapp(res) { |
||||
this.$emit("launchapp", res); |
||||
}, |
||||
}, |
||||
}; |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
/* #ifndef APP-NVUE */ |
||||
@import "./vue.scss"; |
||||
/* #endif */ |
||||
|
||||
/* #ifdef APP-NVUE */ |
||||
@import "./nvue.scss"; |
||||
/* #endif */ |
||||
|
||||
$u-button-u-button-height: 40px !default; |
||||
$u-button-text-font-size: 15px !default; |
||||
$u-button-loading-text-font-size: 15px !default; |
||||
$u-button-loading-text-margin-left: 4px !default; |
||||
$u-button-large-width: 100% !default; |
||||
$u-button-large-height: 50px !default; |
||||
$u-button-normal-padding: 0 12px !default; |
||||
$u-button-large-padding: 0 15px !default; |
||||
$u-button-normal-font-size: 14px !default; |
||||
$u-button-small-min-width: 60px !default; |
||||
$u-button-small-height: 30px !default; |
||||
$u-button-small-padding: 0px 8px !default; |
||||
$u-button-mini-padding: 0px 8px !default; |
||||
$u-button-small-font-size: 12px !default; |
||||
$u-button-mini-height: 22px !default; |
||||
$u-button-mini-font-size: 10px !default; |
||||
$u-button-mini-min-width: 50px !default; |
||||
$u-button-disabled-opacity: 0.5 !default; |
||||
$u-button-info-color: #323233 !default; |
||||
$u-button-info-background-color: #fff !default; |
||||
$u-button-info-border-color: #ebedf0 !default; |
||||
$u-button-info-border-width: 1px !default; |
||||
$u-button-info-border-style: solid !default; |
||||
$u-button-success-color: #fff !default; |
||||
$u-button-success-background-color: $u-success !default; |
||||
$u-button-success-border-color: $u-button-success-background-color !default; |
||||
$u-button-success-border-width: 1px !default; |
||||
$u-button-success-border-style: solid !default; |
||||
$u-button-primary-color: #fff !default; |
||||
$u-button-primary-background-color: $u-primary !default; |
||||
$u-button-primary-border-color: $u-button-primary-background-color !default; |
||||
$u-button-primary-border-width: 1px !default; |
||||
$u-button-primary-border-style: solid !default; |
||||
$u-button-error-color: #fff !default; |
||||
$u-button-error-background-color: $u-error !default; |
||||
$u-button-error-border-color: $u-button-error-background-color !default; |
||||
$u-button-error-border-width: 1px !default; |
||||
$u-button-error-border-style: solid !default; |
||||
$u-button-warning-color: #fff !default; |
||||
$u-button-warning-background-color: $u-warning !default; |
||||
$u-button-warning-border-color: $u-button-warning-background-color !default; |
||||
$u-button-warning-border-width: 1px !default; |
||||
$u-button-warning-border-style: solid !default; |
||||
$u-button-block-width: 100% !default; |
||||
$u-button-circle-border-top-right-radius: 100px !default; |
||||
$u-button-circle-border-top-left-radius: 100px !default; |
||||
$u-button-circle-border-bottom-left-radius: 100px !default; |
||||
$u-button-circle-border-bottom-right-radius: 100px !default; |
||||
$u-button-square-border-top-right-radius: 3px !default; |
||||
$u-button-square-border-top-left-radius: 3px !default; |
||||
$u-button-square-border-bottom-left-radius: 3px !default; |
||||
$u-button-square-border-bottom-right-radius: 3px !default; |
||||
$u-button-icon-min-width: 1em !default; |
||||
$u-button-plain-background-color: #fff !default; |
||||
$u-button-hairline-border-width: 0.5px !default; |
||||
|
||||
.u-button { |
||||
height: $u-button-u-button-height; |
||||
position: relative; |
||||
align-items: center; |
||||
justify-content: center; |
||||
@include flex; |
||||
/* #ifndef APP-NVUE */ |
||||
box-sizing: border-box; |
||||
/* #endif */ |
||||
flex-direction: row; |
||||
|
||||
&__text { |
||||
font-size: $u-button-text-font-size; |
||||
} |
||||
|
||||
&__loading-text { |
||||
font-size: $u-button-loading-text-font-size; |
||||
margin-left: $u-button-loading-text-margin-left; |
||||
} |
||||
|
||||
&--large { |
||||
/* #ifndef APP-NVUE */ |
||||
width: $u-button-large-width; |
||||
/* #endif */ |
||||
height: $u-button-large-height; |
||||
padding: $u-button-large-padding; |
||||
} |
||||
|
||||
&--normal { |
||||
padding: $u-button-normal-padding; |
||||
font-size: $u-button-normal-font-size; |
||||
} |
||||
|
||||
&--small { |
||||
/* #ifndef APP-NVUE */ |
||||
min-width: $u-button-small-min-width; |
||||
/* #endif */ |
||||
height: $u-button-small-height; |
||||
padding: $u-button-small-padding; |
||||
font-size: $u-button-small-font-size; |
||||
} |
||||
|
||||
&--mini { |
||||
height: $u-button-mini-height; |
||||
font-size: $u-button-mini-font-size; |
||||
/* #ifndef APP-NVUE */ |
||||
min-width: $u-button-mini-min-width; |
||||
/* #endif */ |
||||
padding: $u-button-mini-padding; |
||||
} |
||||
|
||||
&--disabled { |
||||
opacity: $u-button-disabled-opacity; |
||||
} |
||||
|
||||
&--info { |
||||
color: $u-button-info-color; |
||||
background-color: $u-button-info-background-color; |
||||
border-color: $u-button-info-border-color; |
||||
border-width: $u-button-info-border-width; |
||||
border-style: $u-button-info-border-style; |
||||
} |
||||
|
||||
&--success { |
||||
color: $u-button-success-color; |
||||
background-color: $u-button-success-background-color; |
||||
border-color: $u-button-success-border-color; |
||||
border-width: $u-button-success-border-width; |
||||
border-style: $u-button-success-border-style; |
||||
} |
||||
|
||||
&--primary { |
||||
color: $u-button-primary-color; |
||||
background-color: $u-button-primary-background-color; |
||||
border-color: $u-button-primary-border-color; |
||||
border-width: $u-button-primary-border-width; |
||||
border-style: $u-button-primary-border-style; |
||||
} |
||||
|
||||
&--error { |
||||
color: $u-button-error-color; |
||||
background-color: $u-button-error-background-color; |
||||
border-color: $u-button-error-border-color; |
||||
border-width: $u-button-error-border-width; |
||||
border-style: $u-button-error-border-style; |
||||
} |
||||
|
||||
&--warning { |
||||
color: $u-button-warning-color; |
||||
background-color: $u-button-warning-background-color; |
||||
border-color: $u-button-warning-border-color; |
||||
border-width: $u-button-warning-border-width; |
||||
border-style: $u-button-warning-border-style; |
||||
} |
||||
|
||||
&--block { |
||||
@include flex; |
||||
width: $u-button-block-width; |
||||
} |
||||
|
||||
&--circle { |
||||
border-top-right-radius: $u-button-circle-border-top-right-radius; |
||||
border-top-left-radius: $u-button-circle-border-top-left-radius; |
||||
border-bottom-left-radius: $u-button-circle-border-bottom-left-radius; |
||||
border-bottom-right-radius: $u-button-circle-border-bottom-right-radius; |
||||
} |
||||
|
||||
&--square { |
||||
border-bottom-left-radius: $u-button-square-border-top-right-radius; |
||||
border-bottom-right-radius: $u-button-square-border-top-left-radius; |
||||
border-top-left-radius: $u-button-square-border-bottom-left-radius; |
||||
border-top-right-radius: $u-button-square-border-bottom-right-radius; |
||||
} |
||||
|
||||
&__icon { |
||||
/* #ifndef APP-NVUE */ |
||||
min-width: $u-button-icon-min-width; |
||||
line-height: inherit !important; |
||||
vertical-align: top; |
||||
/* #endif */ |
||||
} |
||||
|
||||
&--plain { |
||||
background-color: $u-button-plain-background-color; |
||||
} |
||||
|
||||
&--hairline { |
||||
border-width: $u-button-hairline-border-width !important; |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,80 @@ |
||||
// nvue下hover-class无效 |
||||
$u-button-before-top:50% !default; |
||||
$u-button-before-left:50% !default; |
||||
$u-button-before-width:100% !default; |
||||
$u-button-before-height:100% !default; |
||||
$u-button-before-transform:translate(-50%, -50%) !default; |
||||
$u-button-before-opacity:0 !default; |
||||
$u-button-before-background-color:#000 !default; |
||||
$u-button-before-border-color:#000 !default; |
||||
$u-button-active-before-opacity:.15 !default; |
||||
$u-button-icon-margin-left:4px !default; |
||||
$u-button-plain-u-button-info-color:$u-info; |
||||
$u-button-plain-u-button-success-color:$u-success; |
||||
$u-button-plain-u-button-error-color:$u-error; |
||||
$u-button-plain-u-button-warning-color:$u-error; |
||||
|
||||
.u-button { |
||||
width: 100%; |
||||
|
||||
&__text { |
||||
white-space: nowrap; |
||||
line-height: 1; |
||||
} |
||||
|
||||
&:before { |
||||
position: absolute; |
||||
top:$u-button-before-top; |
||||
left:$u-button-before-left; |
||||
width:$u-button-before-width; |
||||
height:$u-button-before-height; |
||||
border: inherit; |
||||
border-radius: inherit; |
||||
transform:$u-button-before-transform; |
||||
opacity:$u-button-before-opacity; |
||||
content: " "; |
||||
background-color:$u-button-before-background-color; |
||||
border-color:$u-button-before-border-color; |
||||
} |
||||
|
||||
&--active { |
||||
&:before { |
||||
opacity: .15 |
||||
} |
||||
} |
||||
|
||||
&__icon+&__text:not(:empty), |
||||
&__loading-text { |
||||
margin-left:$u-button-icon-margin-left; |
||||
} |
||||
|
||||
&--plain { |
||||
&.u-button--primary { |
||||
color: $u-primary; |
||||
} |
||||
} |
||||
|
||||
&--plain { |
||||
&.u-button--info { |
||||
color:$u-button-plain-u-button-info-color; |
||||
} |
||||
} |
||||
|
||||
&--plain { |
||||
&.u-button--success { |
||||
color:$u-button-plain-u-button-success-color; |
||||
} |
||||
} |
||||
|
||||
&--plain { |
||||
&.u-button--error { |
||||
color:$u-button-plain-u-button-error-color; |
||||
} |
||||
} |
||||
|
||||
&--plain { |
||||
&.u-button--warning { |
||||
color:$u-button-plain-u-button-warning-color; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,99 @@ |
||||
<template> |
||||
<view class="u-calendar-header u-border-bottom"> |
||||
<text |
||||
class="u-calendar-header__title" |
||||
v-if="showTitle" |
||||
>{{ title }}</text> |
||||
<text |
||||
class="u-calendar-header__subtitle" |
||||
v-if="showSubtitle" |
||||
>{{ subtitle }}</text> |
||||
<view class="u-calendar-header__weekdays"> |
||||
<text class="u-calendar-header__weekdays__weekday">一</text> |
||||
<text class="u-calendar-header__weekdays__weekday">二</text> |
||||
<text class="u-calendar-header__weekdays__weekday">三</text> |
||||
<text class="u-calendar-header__weekdays__weekday">四</text> |
||||
<text class="u-calendar-header__weekdays__weekday">五</text> |
||||
<text class="u-calendar-header__weekdays__weekday">六</text> |
||||
<text class="u-calendar-header__weekdays__weekday">日</text> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'u-calendar-header', |
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin], |
||||
props: { |
||||
// 标题 |
||||
title: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
// 副标题 |
||||
subtitle: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
// 是否显示标题 |
||||
showTitle: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
// 是否显示副标题 |
||||
showSubtitle: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
}, |
||||
data() { |
||||
return { |
||||
|
||||
} |
||||
}, |
||||
methods: { |
||||
name() { |
||||
|
||||
} |
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
.u-calendar-header { |
||||
padding-bottom: 4px; |
||||
|
||||
&__title { |
||||
font-size: 16px; |
||||
color: $u-main-color; |
||||
text-align: center; |
||||
height: 42px; |
||||
line-height: 42px; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
&__subtitle { |
||||
font-size: 14px; |
||||
color: $u-main-color; |
||||
height: 40px; |
||||
text-align: center; |
||||
line-height: 40px; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
&__weekdays { |
||||
@include flex; |
||||
justify-content: space-between; |
||||
|
||||
&__weekday { |
||||
font-size: 13px; |
||||
color: $u-main-color; |
||||
line-height: 30px; |
||||
flex: 1; |
||||
text-align: center; |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,583 @@ |
||||
<template> |
||||
<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper"> |
||||
<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]" |
||||
:ref="`u-calendar-month-${index}`" :id="`month-${index}`"> |
||||
<text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text> |
||||
<view class="u-calendar-month__days"> |
||||
<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper"> |
||||
<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text> |
||||
</view> |
||||
<view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1" |
||||
:style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)" |
||||
:class="[item1.selected && 'u-calendar-month__days__day__select--selected']"> |
||||
<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]"> |
||||
<text class="u-calendar-month__days__day__select__info" |
||||
:class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']" |
||||
:style="[textStyle(item1)]">{{ item1.day }}</text> |
||||
<text v-if="getBottomInfo(index, index1, item1)" |
||||
class="u-calendar-month__days__day__select__buttom-info" |
||||
:class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']" |
||||
:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text> |
||||
<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
// #ifdef APP-NVUE |
||||
// 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度 |
||||
const dom = uni.requireNativePlugin('dom') |
||||
// #endif |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
import defprops from '../../libs/config/props'; |
||||
// import dayjs from '../../libs/util/dayjs.js'; |
||||
import dayjs from 'dayjs' |
||||
export default { |
||||
name: 'u-calendar-month', |
||||
mixins: [mpMixin, mixin], |
||||
props: { |
||||
// 是否显示月份背景色 |
||||
showMark: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
// 主题色,对底部按钮和选中日期有效 |
||||
color: { |
||||
type: String, |
||||
default: '#3c9cff' |
||||
}, |
||||
// 月份数据 |
||||
months: { |
||||
type: Array, |
||||
default: () => [] |
||||
}, |
||||
// 日期选择类型 |
||||
mode: { |
||||
type: String, |
||||
default: 'single' |
||||
}, |
||||
// 日期行高 |
||||
rowHeight: { |
||||
type: [String, Number], |
||||
default: 58 |
||||
}, |
||||
// mode=multiple时,最多可选多少个日期 |
||||
maxCount: { |
||||
type: [String, Number], |
||||
default: Infinity |
||||
}, |
||||
// mode=range时,第一个日期底部的提示文字 |
||||
startText: { |
||||
type: String, |
||||
default: '开始' |
||||
}, |
||||
// mode=range时,最后一个日期底部的提示文字 |
||||
endText: { |
||||
type: String, |
||||
default: '结束' |
||||
}, |
||||
// 默认选中的日期,mode为multiple或range是必须为数组格式 |
||||
defaultDate: { |
||||
type: [Array, String, Date], |
||||
default: null |
||||
}, |
||||
// 最小的可选日期 |
||||
minDate: { |
||||
type: [String, Number], |
||||
default: 0 |
||||
}, |
||||
// 最大可选日期 |
||||
maxDate: { |
||||
type: [String, Number], |
||||
default: 0 |
||||
}, |
||||
// 如果没有设置maxDate,则往后推多少个月 |
||||
maxMonth: { |
||||
type: [String, Number], |
||||
default: 2 |
||||
}, |
||||
// 是否为只读状态,只读状态下禁止选择日期 |
||||
readonly: { |
||||
type: Boolean, |
||||
default: defprops.calendar.readonly |
||||
}, |
||||
// 日期区间最多可选天数,默认无限制,mode = range时有效 |
||||
maxRange: { |
||||
type: [Number, String], |
||||
default: Infinity |
||||
}, |
||||
// 范围选择超过最多可选天数时的提示文案,mode = range时有效 |
||||
rangePrompt: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 |
||||
showRangePrompt: { |
||||
type: Boolean, |
||||
default: true |
||||
}, |
||||
// 是否允许日期范围的起止时间为同一天,mode = range时有效 |
||||
allowSameDay: { |
||||
type: Boolean, |
||||
default: false |
||||
} |
||||
}, |
||||
data() { |
||||
return { |
||||
// 每个日期的宽度 |
||||
width: 0, |
||||
// 当前选中的日期item |
||||
item: {}, |
||||
selected: [] |
||||
} |
||||
}, |
||||
watch: { |
||||
selectedChange: { |
||||
immediate: true, |
||||
handler(n) { |
||||
this.setDefaultDate() |
||||
} |
||||
} |
||||
}, |
||||
computed: { |
||||
// 多个条件的变化,会引起选中日期的变化,这里统一管理监听 |
||||
selectedChange() { |
||||
return [this.minDate, this.maxDate, this.defaultDate] |
||||
}, |
||||
dayStyle(index1, index2, item) { |
||||
return (index1, index2, item) => { |
||||
const style = {} |
||||
let week = item.week |
||||
// 不进行四舍五入的形式保留2位小数 |
||||
const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1)) |
||||
// 得出每个日期的宽度 |
||||
// #ifdef APP-NVUE |
||||
style.width = uni.$u.addUnit(dayWidth) |
||||
// #endif |
||||
style.height = uni.$u.addUnit(this.rowHeight) |
||||
if (index2 === 0) { |
||||
// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数 |
||||
week = (week === 0 ? 7 : week) - 1 |
||||
style.marginLeft = uni.$u.addUnit(week * dayWidth) |
||||
} |
||||
if (this.mode === 'range') { |
||||
// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug |
||||
style.paddingLeft = 0 |
||||
style.paddingRight = 0 |
||||
style.paddingBottom = 0 |
||||
style.paddingTop = 0 |
||||
} |
||||
return style |
||||
} |
||||
}, |
||||
daySelectStyle() { |
||||
return (index1, index2, item) => { |
||||
let date = dayjs(item.date).format("YYYY-MM-DD"), |
||||
style = {} |
||||
// 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断 |
||||
if (this.selected.some(item => this.dateSame(item, date))) { |
||||
style.backgroundColor = this.color |
||||
} |
||||
if (this.mode === 'single') { |
||||
if (date === this.selected[0]) { |
||||
// 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等 |
||||
style.borderTopLeftRadius = '3px' |
||||
style.borderBottomLeftRadius = '3px' |
||||
style.borderTopRightRadius = '3px' |
||||
style.borderBottomRightRadius = '3px' |
||||
} |
||||
} else if (this.mode === 'range') { |
||||
if (this.selected.length >= 2) { |
||||
const len = this.selected.length - 1 |
||||
// 第一个日期设置左上角和左下角的圆角 |
||||
if (this.dateSame(date, this.selected[0])) { |
||||
style.borderTopLeftRadius = '3px' |
||||
style.borderBottomLeftRadius = '3px' |
||||
} |
||||
// 最后一个日期设置右上角和右下角的圆角 |
||||
if (this.dateSame(date, this.selected[len])) { |
||||
style.borderTopRightRadius = '3px' |
||||
style.borderBottomRightRadius = '3px' |
||||
} |
||||
// 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值 |
||||
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this |
||||
.selected[len]))) { |
||||
style.backgroundColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[90] |
||||
// 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符 |
||||
style.opacity = 0.7 |
||||
} |
||||
} else if (this.selected.length === 1) { |
||||
// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug |
||||
// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现 |
||||
style.borderTopLeftRadius = '3px' |
||||
style.borderBottomLeftRadius = '3px' |
||||
} |
||||
} else { |
||||
if (this.selected.some(item => this.dateSame(item, date))) { |
||||
style.borderTopLeftRadius = '3px' |
||||
style.borderBottomLeftRadius = '3px' |
||||
style.borderTopRightRadius = '3px' |
||||
style.borderBottomRightRadius = '3px' |
||||
} |
||||
} |
||||
return style |
||||
} |
||||
}, |
||||
// 某个日期是否被选中 |
||||
textStyle() { |
||||
return (item) => { |
||||
const date = dayjs(item.date).format("YYYY-MM-DD"), |
||||
style = {} |
||||
// 选中的日期,提示文字设置白色 |
||||
if (this.selected.some(item => this.dateSame(item, date))) { |
||||
style.color = '#ffffff' |
||||
} |
||||
if (this.mode === 'range') { |
||||
const len = this.selected.length - 1 |
||||
// 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色 |
||||
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this |
||||
.selected[len]))) { |
||||
style.color = this.color |
||||
} |
||||
} |
||||
return style |
||||
} |
||||
}, |
||||
// 获取底部的提示文字 |
||||
getBottomInfo() { |
||||
return (index1, index2, item) => { |
||||
const date = dayjs(item.date).format("YYYY-MM-DD") |
||||
const bottomInfo = item.bottomInfo |
||||
// 当为日期范围模式时,且选择的日期个数大于0时 |
||||
if (this.mode === 'range' && this.selected.length > 0) { |
||||
if (this.selected.length === 1) { |
||||
// 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始” |
||||
if (this.dateSame(date, this.selected[0])) return this.startText |
||||
else return bottomInfo |
||||
} else { |
||||
const len = this.selected.length - 1 |
||||
// 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期 |
||||
if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) && |
||||
len === 1) { |
||||
// 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中 |
||||
return `${this.startText}/${this.endText}` |
||||
} else if (this.dateSame(date, this.selected[0])) { |
||||
return this.startText |
||||
} else if (this.dateSame(date, this.selected[len])) { |
||||
return this.endText |
||||
} else { |
||||
return bottomInfo |
||||
} |
||||
} |
||||
} else { |
||||
return bottomInfo |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
mounted() { |
||||
this.init() |
||||
}, |
||||
methods: { |
||||
init() { |
||||
// 初始化默认选中 |
||||
this.$emit('monthSelected', this.selected) |
||||
this.$nextTick(() => { |
||||
// 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度 |
||||
// 因为nvue下,$nextTick并不是100%可靠的 |
||||
uni.$u.sleep(10).then(() => { |
||||
this.getWrapperWidth() |
||||
this.getMonthRect() |
||||
}) |
||||
}) |
||||
}, |
||||
// 判断两个日期是否相等 |
||||
dateSame(date1, date2) { |
||||
return dayjs(date1).isSame(dayjs(date2)) |
||||
}, |
||||
// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度 |
||||
getWrapperWidth() { |
||||
// #ifdef APP-NVUE |
||||
dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => { |
||||
this.width = res.size.width |
||||
}) |
||||
// #endif |
||||
// #ifndef APP-NVUE |
||||
this.$uGetRect('.u-calendar-month-wrapper').then(size => { |
||||
this.width = size.width |
||||
}) |
||||
// #endif |
||||
}, |
||||
getMonthRect() { |
||||
// 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份 |
||||
const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise( |
||||
`u-calendar-month-${index}`)) |
||||
// 一次性返回 |
||||
Promise.all(promiseAllArr).then( |
||||
sizes => { |
||||
let height = 1 |
||||
const topArr = [] |
||||
for (let i = 0; i < this.months.length; i++) { |
||||
// 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份 |
||||
topArr[i] = height |
||||
height += sizes[i].height |
||||
} |
||||
// 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出 |
||||
this.$emit('updateMonthTop', topArr) |
||||
}) |
||||
}, |
||||
// 获取每个月份区域的尺寸 |
||||
getMonthRectByPromise(el) { |
||||
// #ifndef APP-NVUE |
||||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://ijry.github.io/uview-plus/js/getRect.html |
||||
// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同 |
||||
return new Promise(resolve => { |
||||
this.$uGetRect(`.${el}`).then(size => { |
||||
resolve(size) |
||||
}) |
||||
}) |
||||
// #endif |
||||
|
||||
// #ifdef APP-NVUE |
||||
// nvue下,使用dom模块查询元素高度 |
||||
// 返回一个promise,让调用此方法的主体能使用then回调 |
||||
return new Promise(resolve => { |
||||
dom.getComponentRect(this.$refs[el][0], res => { |
||||
resolve(res.size) |
||||
}) |
||||
}) |
||||
// #endif |
||||
}, |
||||
// 点击某一个日期 |
||||
clickHandler(index1, index2, item) { |
||||
if (this.readonly) { |
||||
return; |
||||
} |
||||
this.item = item |
||||
const date = dayjs(item.date).format("YYYY-MM-DD") |
||||
if (item.disabled) return |
||||
// 对上一次选择的日期数组进行深度克隆 |
||||
let selected = uni.$u.deepClone(this.selected) |
||||
if (this.mode === 'single') { |
||||
// 单选情况下,让数组中的元素为当前点击的日期 |
||||
selected = [date] |
||||
} else if (this.mode === 'multiple') { |
||||
if (selected.some(item => this.dateSame(item, date))) { |
||||
// 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果 |
||||
const itemIndex = selected.findIndex(item => item === date) |
||||
selected.splice(itemIndex, 1) |
||||
} else { |
||||
// 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去 |
||||
if (selected.length < this.maxCount) selected.push(date) |
||||
} |
||||
} else { |
||||
// 选择区间形式 |
||||
if (selected.length === 0 || selected.length >= 2) { |
||||
// 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期 |
||||
selected = [date] |
||||
} else if (selected.length === 1) { |
||||
// 如果已经选择了开始日期 |
||||
const existsDate = selected[0] |
||||
// 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期 |
||||
if (dayjs(date).isBefore(existsDate)) { |
||||
selected = [date] |
||||
} else if (dayjs(date).isAfter(existsDate)) { |
||||
// 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示 |
||||
if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) { |
||||
if(this.rangePrompt) { |
||||
uni.$u.toast(this.rangePrompt) |
||||
} else { |
||||
uni.$u.toast(`选择天数不能超过 ${this.maxRange} 天`) |
||||
} |
||||
return |
||||
} |
||||
// 如果当前日期大于已有日期,将当前的添加到数组尾部 |
||||
selected.push(date) |
||||
const startDate = selected[0] |
||||
const endDate = selected[1] |
||||
const arr = [] |
||||
let i = 0 |
||||
do { |
||||
// 将开始和结束日期之间的日期添加到数组中 |
||||
arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD")) |
||||
i++ |
||||
// 累加的日期小于结束日期时,继续下一次的循环 |
||||
} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate))) |
||||
// 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来 |
||||
arr.push(endDate) |
||||
selected = arr |
||||
} else { |
||||
// 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己 |
||||
if (selected[0] === date && !this.allowSameDay) return |
||||
selected.push(date) |
||||
} |
||||
} |
||||
} |
||||
this.setSelected(selected) |
||||
}, |
||||
// 设置默认日期 |
||||
setDefaultDate() { |
||||
if (!this.defaultDate) { |
||||
// 如果没有设置默认日期,则将当天日期设置为默认选中的日期 |
||||
const selected = [dayjs().format("YYYY-MM-DD")] |
||||
return this.setSelected(selected, false) |
||||
} |
||||
let defaultDate = [] |
||||
const minDate = this.minDate || dayjs().format("YYYY-MM-DD") |
||||
const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD") |
||||
if (this.mode === 'single') { |
||||
// 单选模式,可以是字符串或数组,Date对象等 |
||||
if (!uni.$u.test.array(this.defaultDate)) { |
||||
defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")] |
||||
} else { |
||||
defaultDate = [this.defaultDate[0]] |
||||
} |
||||
} else { |
||||
// 如果为非数组,则不执行 |
||||
if (!uni.$u.test.array(this.defaultDate)) return |
||||
defaultDate = this.defaultDate |
||||
} |
||||
// 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素 |
||||
defaultDate = defaultDate.filter(item => { |
||||
return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs( |
||||
maxDate).add(1, 'day')) |
||||
}) |
||||
this.setSelected(defaultDate, false) |
||||
}, |
||||
setSelected(selected, event = true) { |
||||
this.selected = selected |
||||
event && this.$emit('monthSelected', this.selected) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
.u-calendar-month-wrapper { |
||||
margin-top: 4px; |
||||
} |
||||
|
||||
.u-calendar-month { |
||||
|
||||
&__title { |
||||
font-size: 14px; |
||||
line-height: 42px; |
||||
height: 42px; |
||||
color: $u-main-color; |
||||
text-align: center; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
&__days { |
||||
position: relative; |
||||
@include flex; |
||||
flex-wrap: wrap; |
||||
|
||||
&__month-mark-wrapper { |
||||
position: absolute; |
||||
top: 0; |
||||
bottom: 0; |
||||
left: 0; |
||||
right: 0; |
||||
@include flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
|
||||
&__text { |
||||
font-size: 155px; |
||||
color: rgba(231, 232, 234, 0.83); |
||||
} |
||||
} |
||||
|
||||
&__day { |
||||
@include flex; |
||||
padding: 2px; |
||||
/* #ifndef APP-NVUE */ |
||||
// vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移 |
||||
width: calc(100% / 7); |
||||
box-sizing: border-box; |
||||
/* #endif */ |
||||
|
||||
&__select { |
||||
flex: 1; |
||||
@include flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
position: relative; |
||||
|
||||
&__dot { |
||||
width: 7px; |
||||
height: 7px; |
||||
border-radius: 100px; |
||||
background-color: $u-error; |
||||
position: absolute; |
||||
top: 12px; |
||||
right: 7px; |
||||
} |
||||
|
||||
&__buttom-info { |
||||
color: $u-content-color; |
||||
text-align: center; |
||||
position: absolute; |
||||
bottom: 5px; |
||||
font-size: 10px; |
||||
text-align: center; |
||||
left: 0; |
||||
right: 0; |
||||
|
||||
&--selected { |
||||
color: #ffffff; |
||||
} |
||||
|
||||
&--disabled { |
||||
color: #cacbcd; |
||||
} |
||||
} |
||||
|
||||
&__info { |
||||
text-align: center; |
||||
font-size: 16px; |
||||
|
||||
&--selected { |
||||
color: #ffffff; |
||||
} |
||||
|
||||
&--disabled { |
||||
color: #cacbcd; |
||||
} |
||||
} |
||||
|
||||
&--selected { |
||||
background-color: $u-primary; |
||||
@include flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
flex: 1; |
||||
border-radius: 3px; |
||||
} |
||||
|
||||
&--range-selected { |
||||
opacity: 0.3; |
||||
border-radius: 0; |
||||
} |
||||
|
||||
&--range-start-selected { |
||||
border-top-right-radius: 0; |
||||
border-bottom-right-radius: 0; |
||||
} |
||||
|
||||
&--range-end-selected { |
||||
border-top-left-radius: 0; |
||||
border-bottom-left-radius: 0; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,145 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 日历顶部标题
|
||||
title: { |
||||
type: String, |
||||
default: defprops.calendar.title |
||||
}, |
||||
// 是否显示标题
|
||||
showTitle: { |
||||
type: Boolean, |
||||
default: defprops.calendar.showTitle |
||||
}, |
||||
// 是否显示副标题
|
||||
showSubtitle: { |
||||
type: Boolean, |
||||
default: defprops.calendar.showSubtitle |
||||
}, |
||||
// 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
|
||||
mode: { |
||||
type: String, |
||||
default: defprops.calendar.mode |
||||
}, |
||||
// mode=range时,第一个日期底部的提示文字
|
||||
startText: { |
||||
type: String, |
||||
default: defprops.calendar.startText |
||||
}, |
||||
// mode=range时,最后一个日期底部的提示文字
|
||||
endText: { |
||||
type: String, |
||||
default: defprops.calendar.endText |
||||
}, |
||||
// 自定义列表
|
||||
customList: { |
||||
type: Array, |
||||
default: defprops.calendar.customList |
||||
}, |
||||
// 主题色,对底部按钮和选中日期有效
|
||||
color: { |
||||
type: String, |
||||
default: defprops.calendar.color |
||||
}, |
||||
// 最小的可选日期
|
||||
minDate: { |
||||
type: [String, Number], |
||||
default: defprops.calendar.minDate |
||||
}, |
||||
// 最大可选日期
|
||||
maxDate: { |
||||
type: [String, Number], |
||||
default: defprops.calendar.maxDate |
||||
}, |
||||
// 默认选中的日期,mode为multiple或range是必须为数组格式
|
||||
defaultDate: { |
||||
type: [Array, String, Date, null], |
||||
default: defprops.calendar.defaultDate |
||||
}, |
||||
// mode=multiple时,最多可选多少个日期
|
||||
maxCount: { |
||||
type: [String, Number], |
||||
default: defprops.calendar.maxCount |
||||
}, |
||||
// 日期行高
|
||||
rowHeight: { |
||||
type: [String, Number], |
||||
default: defprops.calendar.rowHeight |
||||
}, |
||||
// 日期格式化函数
|
||||
formatter: { |
||||
type: [Function, null], |
||||
default: defprops.calendar.formatter |
||||
}, |
||||
// 是否显示农历
|
||||
showLunar: { |
||||
type: Boolean, |
||||
default: defprops.calendar.showLunar |
||||
}, |
||||
// 是否显示月份背景色
|
||||
showMark: { |
||||
type: Boolean, |
||||
default: defprops.calendar.showMark |
||||
}, |
||||
// 确定按钮的文字
|
||||
confirmText: { |
||||
type: String, |
||||
default: defprops.calendar.confirmText |
||||
}, |
||||
// 确认按钮处于禁用状态时的文字
|
||||
confirmDisabledText: { |
||||
type: String, |
||||
default: defprops.calendar.confirmDisabledText |
||||
}, |
||||
// 是否显示日历弹窗
|
||||
show: { |
||||
type: Boolean, |
||||
default: defprops.calendar.show |
||||
}, |
||||
// 是否允许点击遮罩关闭日历
|
||||
closeOnClickOverlay: { |
||||
type: Boolean, |
||||
default: defprops.calendar.closeOnClickOverlay |
||||
}, |
||||
// 是否为只读状态,只读状态下禁止选择日期
|
||||
readonly: { |
||||
type: Boolean, |
||||
default: defprops.calendar.readonly |
||||
}, |
||||
// 是否展示确认按钮
|
||||
showConfirm: { |
||||
type: Boolean, |
||||
default: defprops.calendar.showConfirm |
||||
}, |
||||
// 日期区间最多可选天数,默认无限制,mode = range时有效
|
||||
maxRange: { |
||||
type: [Number, String], |
||||
default: defprops.calendar.maxRange |
||||
}, |
||||
// 范围选择超过最多可选天数时的提示文案,mode = range时有效
|
||||
rangePrompt: { |
||||
type: String, |
||||
default: defprops.calendar.rangePrompt |
||||
}, |
||||
// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
|
||||
showRangePrompt: { |
||||
type: Boolean, |
||||
default: defprops.calendar.showRangePrompt |
||||
}, |
||||
// 是否允许日期范围的起止时间为同一天,mode = range时有效
|
||||
allowSameDay: { |
||||
type: Boolean, |
||||
default: defprops.calendar.allowSameDay |
||||
}, |
||||
// 圆角值
|
||||
round: { |
||||
type: [Boolean, String, Number], |
||||
default: defprops.calendar.round |
||||
}, |
||||
// 最多展示月份数量
|
||||
monthNum: { |
||||
type: [Number, String], |
||||
default: 3 |
||||
}
|
||||
} |
||||
} |
||||
@ -0,0 +1,386 @@ |
||||
<template> |
||||
<u-popup |
||||
:show="show" |
||||
mode="bottom" |
||||
closeable |
||||
@close="close" |
||||
:round="round" |
||||
:closeOnClickOverlay="closeOnClickOverlay" |
||||
> |
||||
<view class="u-calendar"> |
||||
<uHeader |
||||
:title="title" |
||||
:subtitle="subtitle" |
||||
:showSubtitle="showSubtitle" |
||||
:showTitle="showTitle" |
||||
></uHeader> |
||||
<scroll-view |
||||
:style="{ |
||||
height: $u.addUnit(listHeight) |
||||
}" |
||||
scroll-y |
||||
@scroll="onScroll" |
||||
:scroll-top="scrollTop" |
||||
:scrollIntoView="scrollIntoView" |
||||
> |
||||
<uMonth |
||||
:color="color" |
||||
:rowHeight="rowHeight" |
||||
:showMark="showMark" |
||||
:months="months" |
||||
:mode="mode" |
||||
:maxCount="maxCount" |
||||
:startText="startText" |
||||
:endText="endText" |
||||
:defaultDate="defaultDate" |
||||
:minDate="innerMinDate" |
||||
:maxDate="innerMaxDate" |
||||
:maxMonth="monthNum" |
||||
:readonly="readonly" |
||||
:maxRange="maxRange" |
||||
:rangePrompt="rangePrompt" |
||||
:showRangePrompt="showRangePrompt" |
||||
:allowSameDay="allowSameDay" |
||||
ref="month" |
||||
@monthSelected="monthSelected" |
||||
@updateMonthTop="updateMonthTop" |
||||
></uMonth> |
||||
</scroll-view> |
||||
<slot name="footer" v-if="showConfirm"> |
||||
<view class="u-calendar__confirm"> |
||||
<u-button |
||||
shape="circle" |
||||
:text=" |
||||
buttonDisabled ? confirmDisabledText : confirmText |
||||
" |
||||
:color="color" |
||||
@click="confirm" |
||||
:disabled="buttonDisabled" |
||||
></u-button> |
||||
</view> |
||||
</slot> |
||||
</view> |
||||
</u-popup> |
||||
</template> |
||||
|
||||
<script> |
||||
import uHeader from './header.vue' |
||||
import uMonth from './month.vue' |
||||
import props from './props.js' |
||||
import util from './util.js' |
||||
// import dayjs from '../../libs/util/dayjs.min.js' |
||||
import dayjs from 'dayjs' |
||||
import Calendar from '../../libs/util/calendar.js' |
||||
import mpMixin from '../../libs/mixin/mpMixin.js' |
||||
import mixin from '../../libs/mixin/mixin.js' |
||||
/** |
||||
* Calendar 日历 |
||||
* @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中. |
||||
* @tutorial https://ijry.github.io/uview-plus/components/calendar.html |
||||
* |
||||
* @property {String} title 标题内容 (默认 日期选择 ) |
||||
* @property {Boolean} showTitle 是否显示标题 (默认 true ) |
||||
* @property {Boolean} showSubtitle 是否显示副标题 (默认 true ) |
||||
* @property {String} mode 日期类型选择 single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' ) |
||||
* @property {String} startText mode=range时,第一个日期底部的提示文字 (默认 '开始' ) |
||||
* @property {String} endText mode=range时,最后一个日期底部的提示文字 (默认 '结束' ) |
||||
* @property {Array} customList 自定义列表 |
||||
* @property {String} color 主题色,对底部按钮和选中日期有效 (默认 ‘#3c9cff' ) |
||||
* @property {String | Number} minDate 最小的可选日期 (默认 0 ) |
||||
* @property {String | Number} maxDate 最大可选日期 (默认 0 ) |
||||
* @property {Array | String| Date} defaultDate 默认选中的日期,mode为multiple或range是必须为数组格式 |
||||
* @property {String | Number} maxCount mode=multiple时,最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER ) |
||||
* @property {String | Number} rowHeight 日期行高 (默认 56 ) |
||||
* @property {Function} formatter 日期格式化函数 |
||||
* @property {Boolean} showLunar 是否显示农历 (默认 false ) |
||||
* @property {Boolean} showMark 是否显示月份背景色 (默认 true ) |
||||
* @property {String} confirmText 确定按钮的文字 (默认 '确定' ) |
||||
* @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' ) |
||||
* @property {Boolean} show 是否显示日历弹窗 (默认 false ) |
||||
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false ) |
||||
* @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false ) |
||||
* @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效 |
||||
* @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效 |
||||
* @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true ) |
||||
* @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false ) |
||||
* @property {Number|String} round 圆角值,默认无圆角 (默认 0 ) |
||||
* @property {Number|String} monthNum 最多展示的月份数量 (默认 3 ) |
||||
* |
||||
* @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数 |
||||
* @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件 |
||||
* @example <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm"> |
||||
</u-calendar> |
||||
* */ |
||||
export default { |
||||
name: 'u-calendar', |
||||
mixins: [mpMixin, mixin, props], |
||||
components: { |
||||
uHeader, |
||||
uMonth |
||||
}, |
||||
data() { |
||||
return { |
||||
// 需要显示的月份的数组 |
||||
months: [], |
||||
// 在月份滚动区域中,当前视图中月份的index索引 |
||||
monthIndex: 0, |
||||
// 月份滚动区域的高度 |
||||
listHeight: 0, |
||||
// month组件中选择的日期数组 |
||||
selected: [], |
||||
scrollIntoView: '', |
||||
scrollTop:0, |
||||
// 过滤处理方法 |
||||
innerFormatter: (value) => value |
||||
} |
||||
}, |
||||
watch: { |
||||
selectedChange: { |
||||
immediate: true, |
||||
handler(n) { |
||||
this.setMonth() |
||||
} |
||||
}, |
||||
// 打开弹窗时,设置月份数据 |
||||
show: { |
||||
immediate: true, |
||||
handler(n) { |
||||
this.setMonth() |
||||
} |
||||
} |
||||
}, |
||||
computed: { |
||||
// 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理 |
||||
innerMaxDate() { |
||||
return uni.$u.test.number(this.maxDate) |
||||
? Number(this.maxDate) |
||||
: this.maxDate |
||||
}, |
||||
innerMinDate() { |
||||
return uni.$u.test.number(this.minDate) |
||||
? Number(this.minDate) |
||||
: this.minDate |
||||
}, |
||||
// 多个条件的变化,会引起选中日期的变化,这里统一管理监听 |
||||
selectedChange() { |
||||
return [this.innerMinDate, this.innerMaxDate, this.defaultDate] |
||||
}, |
||||
subtitle() { |
||||
// 初始化时,this.months为空数组,所以需要特别判断处理 |
||||
if (this.months.length) { |
||||
return `${this.months[this.monthIndex].year}年${ |
||||
this.months[this.monthIndex].month |
||||
}月` |
||||
} else { |
||||
return '' |
||||
} |
||||
}, |
||||
buttonDisabled() { |
||||
// 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态 |
||||
if (this.mode === 'range') { |
||||
if (this.selected.length <= 1) { |
||||
return true |
||||
} else { |
||||
return false |
||||
} |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
}, |
||||
mounted() { |
||||
this.start = Date.now() |
||||
this.init() |
||||
}, |
||||
methods: { |
||||
// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用 |
||||
setFormatter(e) { |
||||
this.innerFormatter = e |
||||
}, |
||||
// month组件内部选择日期后,通过事件通知给父组件 |
||||
monthSelected(e) { |
||||
this.selected = e |
||||
if (!this.showConfirm) { |
||||
// 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还 |
||||
if ( |
||||
this.mode === 'multiple' || |
||||
this.mode === 'single' || |
||||
(this.mode === 'range' && this.selected.length >= 2) |
||||
) { |
||||
this.$emit('confirm', this.selected) |
||||
} |
||||
} |
||||
}, |
||||
init() { |
||||
// 校验maxDate,不能小于当前时间 |
||||
if ( |
||||
this.innerMaxDate && |
||||
new Date(this.innerMaxDate).getTime() <= Date.now() |
||||
) { |
||||
return uni.$u.error('maxDate不能小于当前时间') |
||||
} |
||||
// 滚动区域的高度 |
||||
this.listHeight = this.rowHeight * 5 + 30 |
||||
this.setMonth() |
||||
}, |
||||
close() { |
||||
this.$emit('close') |
||||
}, |
||||
// 点击确定按钮 |
||||
confirm() { |
||||
if (!this.buttonDisabled) { |
||||
this.$emit('confirm', this.selected) |
||||
} |
||||
}, |
||||
// 获得两个日期之间的月份数 |
||||
getMonths(minDate, maxDate) { |
||||
const minYear = dayjs(minDate).year() |
||||
const minMonth = dayjs(minDate).month() + 1 |
||||
const maxYear = dayjs(maxDate).year() |
||||
const maxMonth = dayjs(maxDate).month() + 1 |
||||
return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1 |
||||
}, |
||||
// 设置月份数据 |
||||
setMonth() { |
||||
// 最小日期的毫秒数 |
||||
const minDate = this.innerMinDate || dayjs().valueOf() |
||||
// 如果没有指定最大日期,则往后推3个月 |
||||
const maxDate = |
||||
this.innerMaxDate || |
||||
dayjs(minDate) |
||||
.add(this.monthNum - 1, 'month') |
||||
.valueOf() |
||||
// 最大最小月份之间的共有多少个月份, |
||||
const months = uni.$u.range( |
||||
1, |
||||
this.monthNum, |
||||
this.getMonths(minDate, maxDate) |
||||
) |
||||
// 先清空数组 |
||||
this.months = [] |
||||
for (let i = 0; i < months; i++) { |
||||
this.months.push({ |
||||
date: new Array( |
||||
dayjs(minDate).add(i, 'month').daysInMonth() |
||||
) |
||||
.fill(1) |
||||
.map((item, index) => { |
||||
// 日期,取值1-31 |
||||
let day = index + 1 |
||||
// 星期,0-6,0为周日 |
||||
const week = dayjs(minDate) |
||||
.add(i, 'month') |
||||
.date(day) |
||||
.day() |
||||
const date = dayjs(minDate) |
||||
.add(i, 'month') |
||||
.date(day) |
||||
.format('YYYY-MM-DD') |
||||
let bottomInfo = '' |
||||
if (this.showLunar) { |
||||
// 将日期转为农历格式 |
||||
const lunar = Calendar.solar2lunar( |
||||
dayjs(date).year(), |
||||
dayjs(date).month() + 1, |
||||
dayjs(date).date() |
||||
) |
||||
bottomInfo = lunar.IDayCn |
||||
} |
||||
let config = { |
||||
day, |
||||
week, |
||||
// 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态 |
||||
disabled: |
||||
dayjs(date).isBefore( |
||||
dayjs(minDate).format('YYYY-MM-DD') |
||||
) || |
||||
dayjs(date).isAfter( |
||||
dayjs(maxDate).format('YYYY-MM-DD') |
||||
), |
||||
// 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理 |
||||
date: new Date(date), |
||||
bottomInfo, |
||||
dot: false, |
||||
month: |
||||
dayjs(minDate).add(i, 'month').month() + 1 |
||||
} |
||||
const formatter = |
||||
this.formatter || this.innerFormatter |
||||
return formatter(config) |
||||
}), |
||||
// 当前所属的月份 |
||||
month: dayjs(minDate).add(i, 'month').month() + 1, |
||||
// 当前年份 |
||||
year: dayjs(minDate).add(i, 'month').year() |
||||
}) |
||||
} |
||||
|
||||
}, |
||||
// 滚动到默认设置的月份 |
||||
scrollIntoDefaultMonth(selected) { |
||||
// 查询默认日期在可选列表的下标 |
||||
const _index = this.months.findIndex(({ |
||||
year, |
||||
month |
||||
}) => { |
||||
month = uni.$u.padZero(month) |
||||
return `${year}-${month}` === selected |
||||
}) |
||||
if (_index !== -1) { |
||||
// #ifndef MP-WEIXIN |
||||
this.$nextTick(() => { |
||||
this.scrollIntoView = `month-${_index}` |
||||
}) |
||||
// #endif |
||||
// #ifdef MP-WEIXIN |
||||
this.scrollTop = this.months[_index].top || 0; |
||||
// #endif |
||||
} |
||||
}, |
||||
// scroll-view滚动监听 |
||||
onScroll(event) { |
||||
// 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值 |
||||
const scrollTop = Math.max(0, event.detail.scrollTop) |
||||
// 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引 |
||||
for (let i = 0; i < this.months.length; i++) { |
||||
if (scrollTop >= (this.months[i].top || this.listHeight)) { |
||||
this.monthIndex = i |
||||
} |
||||
} |
||||
}, |
||||
// 更新月份的top值 |
||||
updateMonthTop(topArr = []) { |
||||
// 设置对应月份的top值,用于onScroll方法更新月份 |
||||
topArr.map((item, index) => { |
||||
this.months[index].top = item |
||||
}) |
||||
|
||||
// 获取默认日期的下标 |
||||
if (!this.defaultDate) { |
||||
// 如果没有设置默认日期,则将当天日期设置为默认选中的日期 |
||||
const selected = dayjs().format("YYYY-MM") |
||||
this.scrollIntoDefaultMonth(selected) |
||||
return |
||||
} |
||||
let selected = dayjs().format("YYYY-MM"); |
||||
// 单选模式,可以是字符串或数组,Date对象等 |
||||
if (!uni.$u.test.array(this.defaultDate)) { |
||||
selected = dayjs(this.defaultDate).format("YYYY-MM") |
||||
} else { |
||||
selected = dayjs(this.defaultDate[0]).format("YYYY-MM"); |
||||
} |
||||
this.scrollIntoDefaultMonth(selected) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import '../../libs/css/components.scss'; |
||||
|
||||
.u-calendar { |
||||
&__confirm { |
||||
padding: 7px 18px; |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,86 @@ |
||||
import dayjs from 'dayjs' |
||||
export default { |
||||
methods: { |
||||
// 设置月份数据
|
||||
setMonth() { |
||||
// 月初是周几
|
||||
const day = dayjs(this.date).date(1).day() |
||||
const start = day == 0 ? 6 : day - 1 |
||||
|
||||
// 本月天数
|
||||
const days = dayjs(this.date).endOf('month').format('D') |
||||
|
||||
// 上个月天数
|
||||
const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D') |
||||
|
||||
// 日期数据
|
||||
const arr = [] |
||||
// 清空表格
|
||||
this.month = [] |
||||
|
||||
// 添加上月数据
|
||||
arr.push( |
||||
...new Array(start).fill(1).map((e, i) => { |
||||
const day = prevDays - start + i + 1 |
||||
|
||||
return { |
||||
value: day, |
||||
disabled: true, |
||||
date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD') |
||||
} |
||||
}) |
||||
) |
||||
|
||||
// 添加本月数据
|
||||
arr.push( |
||||
...new Array(days - 0).fill(1).map((e, i) => { |
||||
const day = i + 1 |
||||
|
||||
return { |
||||
value: day, |
||||
date: dayjs(this.date).date(day).format('YYYY-MM-DD') |
||||
} |
||||
}) |
||||
) |
||||
|
||||
// 添加下个月
|
||||
arr.push( |
||||
...new Array(42 - days - start).fill(1).map((e, i) => { |
||||
const day = i + 1 |
||||
|
||||
return { |
||||
value: day, |
||||
disabled: true, |
||||
date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD') |
||||
} |
||||
}) |
||||
) |
||||
|
||||
// 分割数组
|
||||
for (let n = 0; n < arr.length; n += 7) { |
||||
this.month.push( |
||||
arr.slice(n, n + 7).map((e, i) => { |
||||
e.index = i + n |
||||
|
||||
// 自定义信息
|
||||
const custom = this.customList.find((c) => c.date == e.date) |
||||
|
||||
// 农历
|
||||
if (this.lunar) { |
||||
const { |
||||
IDayCn, |
||||
IMonthCn |
||||
} = this.getLunar(e.date) |
||||
e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn |
||||
} |
||||
|
||||
return { |
||||
...e, |
||||
...custom |
||||
} |
||||
}) |
||||
) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,15 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 是否打乱键盘按键的顺序
|
||||
random: { |
||||
type: Boolean, |
||||
default: false |
||||
}, |
||||
// 输入一个中文后,是否自动切换到英文
|
||||
autoChange: { |
||||
type: Boolean, |
||||
default: false |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,313 @@ |
||||
<template> |
||||
<view |
||||
class="u-keyboard" |
||||
@touchmove.stop.prevent="noop" |
||||
> |
||||
<view |
||||
v-for="(group, i) in abc ? engKeyBoardList : areaList" |
||||
:key="i" |
||||
class="u-keyboard__button" |
||||
:index="i" |
||||
:class="[i + 1 === 4 && 'u-keyboard__button--center']" |
||||
> |
||||
<view |
||||
v-if="i === 3" |
||||
class="u-keyboard__button__inner-wrapper" |
||||
> |
||||
<view |
||||
class="u-keyboard__button__inner-wrapper__left" |
||||
hover-class="u-hover-class" |
||||
:hover-stay-time="200" |
||||
@tap="changeCarInputMode" |
||||
> |
||||
<text |
||||
class="u-keyboard__button__inner-wrapper__left__lang" |
||||
:class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']" |
||||
>中</text> |
||||
<text class="u-keyboard__button__inner-wrapper__left__line">/</text> |
||||
<text |
||||
class="u-keyboard__button__inner-wrapper__left__lang" |
||||
:class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']" |
||||
>英</text> |
||||
</view> |
||||
</view> |
||||
<view |
||||
class="u-keyboard__button__inner-wrapper" |
||||
v-for="(item, j) in group" |
||||
:key="j" |
||||
> |
||||
<view |
||||
class="u-keyboard__button__inner-wrapper__inner" |
||||
:hover-stay-time="200" |
||||
@tap="carInputClick(i, j)" |
||||
hover-class="u-hover-class" |
||||
> |
||||
<text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text> |
||||
</view> |
||||
</view> |
||||
<view |
||||
v-if="i === 3" |
||||
@touchstart="backspaceClick" |
||||
@touchend="clearTimer" |
||||
class="u-keyboard__button__inner-wrapper" |
||||
> |
||||
<view |
||||
class="u-keyboard__button__inner-wrapper__right" |
||||
hover-class="u-hover-class" |
||||
:hover-stay-time="200" |
||||
> |
||||
<u-icon |
||||
size="28" |
||||
name="backspace" |
||||
color="#303133" |
||||
></u-icon> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* keyboard 键盘组件 |
||||
* @description 此为uView自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。 |
||||
* @tutorial https://uviewui.com/components/keyboard.html |
||||
* @property {Boolean} random 是否打乱键盘的顺序 |
||||
* @event {Function} change 点击键盘触发 |
||||
* @event {Function} backspace 点击退格键触发 |
||||
* @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard> |
||||
*/ |
||||
export default { |
||||
name: "u-keyboard", |
||||
mixins: [mpMixin, mixin, props], |
||||
data() { |
||||
return { |
||||
// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称 |
||||
abc: false |
||||
}; |
||||
}, |
||||
computed: { |
||||
areaList() { |
||||
let data = [ |
||||
'京', |
||||
'沪', |
||||
'粤', |
||||
'津', |
||||
'冀', |
||||
'豫', |
||||
'云', |
||||
'辽', |
||||
'黑', |
||||
'湘', |
||||
'皖', |
||||
'鲁', |
||||
'苏', |
||||
'浙', |
||||
'赣', |
||||
'鄂', |
||||
'桂', |
||||
'甘', |
||||
'晋', |
||||
'陕', |
||||
'蒙', |
||||
'吉', |
||||
'闽', |
||||
'贵', |
||||
'渝', |
||||
'川', |
||||
'青', |
||||
'琼', |
||||
'宁', |
||||
'挂', |
||||
'藏', |
||||
'港', |
||||
'澳', |
||||
'新', |
||||
'使', |
||||
'学' |
||||
]; |
||||
let tmp = []; |
||||
// 打乱顺序 |
||||
if (this.random) data = uni.$u.randomArray(data); |
||||
// 切割成二维数组 |
||||
tmp[0] = data.slice(0, 10); |
||||
tmp[1] = data.slice(10, 20); |
||||
tmp[2] = data.slice(20, 30); |
||||
tmp[3] = data.slice(30, 36); |
||||
return tmp; |
||||
}, |
||||
engKeyBoardList() { |
||||
let data = [ |
||||
1, |
||||
2, |
||||
3, |
||||
4, |
||||
5, |
||||
6, |
||||
7, |
||||
8, |
||||
9, |
||||
0, |
||||
'Q', |
||||
'W', |
||||
'E', |
||||
'R', |
||||
'T', |
||||
'Y', |
||||
'U', |
||||
'I', |
||||
'O', |
||||
'P', |
||||
'A', |
||||
'S', |
||||
'D', |
||||
'F', |
||||
'G', |
||||
'H', |
||||
'J', |
||||
'K', |
||||
'L', |
||||
'Z', |
||||
'X', |
||||
'C', |
||||
'V', |
||||
'B', |
||||
'N', |
||||
'M' |
||||
]; |
||||
let tmp = []; |
||||
if (this.random) data = uni.$u.randomArray(data); |
||||
tmp[0] = data.slice(0, 10); |
||||
tmp[1] = data.slice(10, 20); |
||||
tmp[2] = data.slice(20, 30); |
||||
tmp[3] = data.slice(30, 36); |
||||
return tmp; |
||||
} |
||||
}, |
||||
methods: { |
||||
// 点击键盘按钮 |
||||
carInputClick(i, j) { |
||||
let value = ''; |
||||
// 不同模式,获取不同数组的值 |
||||
if (this.abc) value = this.engKeyBoardList[i][j]; |
||||
else value = this.areaList[i][j]; |
||||
// 如果允许自动切换,则将中文状态切换为英文 |
||||
if (!this.abc && this.autoChange) uni.$u.sleep(200).then(() => this.abc = true) |
||||
this.$emit('change', value); |
||||
}, |
||||
// 修改汽车牌键盘的输入模式,中文|英文 |
||||
changeCarInputMode() { |
||||
this.abc = !this.abc; |
||||
}, |
||||
// 点击退格键 |
||||
backspaceClick() { |
||||
this.$emit('backspace'); |
||||
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器 |
||||
this.timer = null; |
||||
this.timer = setInterval(() => { |
||||
this.$emit('backspace'); |
||||
}, 250); |
||||
}, |
||||
clearTimer() { |
||||
clearInterval(this.timer); |
||||
this.timer = null; |
||||
}, |
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
$u-car-keyboard-background-color: rgb(224, 228, 230) !default; |
||||
$u-car-keyboard-padding:6px 0 6px !default; |
||||
$u-car-keyboard-button-inner-width:64rpx !default; |
||||
$u-car-keyboard-button-inner-background-color:#FFFFFF !default; |
||||
$u-car-keyboard-button-height:80rpx !default; |
||||
$u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default; |
||||
$u-car-keyboard-button-border-radius:4px !default; |
||||
$u-car-keyboard-button-inner-margin:8rpx 5rpx !default; |
||||
$u-car-keyboard-button-text-font-size:16px !default; |
||||
$u-car-keyboard-button-text-color:$u-main-color !default; |
||||
$u-car-keyboard-center-inner-margin: 0 4rpx !default; |
||||
$u-car-keyboard-special-button-width:134rpx !default; |
||||
$u-car-keyboard-lang-font-size:16px !default; |
||||
$u-car-keyboard-lang-color:$u-main-color !default; |
||||
$u-car-keyboard-active-color:$u-primary !default; |
||||
$u-car-keyboard-line-font-size:15px !default; |
||||
$u-car-keyboard-line-color:$u-main-color !default; |
||||
$u-car-keyboard-line-margin:0 1px !default; |
||||
$u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default; |
||||
|
||||
.u-keyboard { |
||||
@include flex(column); |
||||
justify-content: space-around; |
||||
background-color: $u-car-keyboard-background-color; |
||||
align-items: stretch; |
||||
padding: $u-car-keyboard-padding; |
||||
|
||||
&__button { |
||||
@include flex; |
||||
justify-content: center; |
||||
flex: 1; |
||||
/* #ifndef APP-NVUE */ |
||||
/* #endif */ |
||||
|
||||
&__inner-wrapper { |
||||
box-shadow: $u-car-keyboard-button-inner-box-shadow; |
||||
margin: $u-car-keyboard-button-inner-margin; |
||||
border-radius: $u-car-keyboard-button-border-radius; |
||||
|
||||
&__inner { |
||||
@include flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
width: $u-car-keyboard-button-inner-width; |
||||
background-color: $u-car-keyboard-button-inner-background-color; |
||||
height: $u-car-keyboard-button-height; |
||||
border-radius: $u-car-keyboard-button-border-radius; |
||||
|
||||
&__text { |
||||
font-size: $u-car-keyboard-button-text-font-size; |
||||
color: $u-car-keyboard-button-text-color; |
||||
} |
||||
} |
||||
|
||||
&__left, |
||||
&__right { |
||||
border-radius: $u-car-keyboard-button-border-radius; |
||||
width: $u-car-keyboard-special-button-width; |
||||
height: $u-car-keyboard-button-height; |
||||
background-color: $u-car-keyboard-u-hover-class-background-color; |
||||
@include flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
box-shadow: $u-car-keyboard-button-inner-box-shadow; |
||||
} |
||||
|
||||
&__left { |
||||
&__line { |
||||
font-size: $u-car-keyboard-line-font-size; |
||||
color: $u-car-keyboard-line-color; |
||||
margin: $u-car-keyboard-line-margin; |
||||
} |
||||
|
||||
&__lang { |
||||
font-size: $u-car-keyboard-lang-font-size; |
||||
color: $u-car-keyboard-lang-color; |
||||
|
||||
&--active { |
||||
color: $u-car-keyboard-active-color; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.u-hover-class { |
||||
background-color: $u-car-keyboard-u-hover-class-background-color; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,15 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 分组标题
|
||||
title: { |
||||
type: String, |
||||
default: defprops.cellGroup.title |
||||
}, |
||||
// 是否显示外边框
|
||||
border: { |
||||
type: Boolean, |
||||
default: defprops.cellGroup.border |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,63 @@ |
||||
<template> |
||||
<view :style="[$u.addStyle(customStyle)]" :class="[customClass]" class="u-cell-group"> |
||||
<view v-if="title" class="u-cell-group__title"> |
||||
<slot name="title"> |
||||
<text class="u-cell-group__title__text">{{ title }}</text> |
||||
</slot> |
||||
</view> |
||||
<view class="u-cell-group__wrapper"> |
||||
<u-line v-if="border"></u-line> |
||||
<slot /> |
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* cellGroup 单元格 |
||||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。 |
||||
* @tutorial https://uviewui.com/components/cell.html |
||||
* |
||||
* @property {String} title 分组标题 |
||||
* @property {Boolean} border 是否显示外边框 (默认 true ) |
||||
* @property {Object} customStyle 定义需要用到的外部样式 |
||||
* |
||||
* @event {Function} click 点击cell列表时触发 |
||||
* @example <u-cell-group title="设置喜好"> |
||||
*/ |
||||
export default { |
||||
name: 'u-cell-group', |
||||
mixins: [mpMixin, mixin, props], |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
$u-cell-group-title-padding: 16px 16px 8px !default; |
||||
$u-cell-group-title-font-size: 15px !default; |
||||
$u-cell-group-title-line-height: 16px !default; |
||||
$u-cell-group-title-color: $u-main-color !default; |
||||
|
||||
.u-cell-group { |
||||
flex: 1; |
||||
|
||||
&__title { |
||||
padding: $u-cell-group-title-padding; |
||||
|
||||
&__text { |
||||
font-size: $u-cell-group-title-font-size; |
||||
line-height: $u-cell-group-title-line-height; |
||||
color: $u-cell-group-title-color; |
||||
} |
||||
} |
||||
|
||||
&__wrapper { |
||||
position: relative; |
||||
} |
||||
} |
||||
</style> |
||||
|
||||
@ -0,0 +1,111 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 标题
|
||||
title: { |
||||
type: [String, Number], |
||||
default: defprops.cell.title |
||||
}, |
||||
// 标题下方的描述信息
|
||||
label: { |
||||
type: [String, Number], |
||||
default: defprops.cell.label |
||||
}, |
||||
// 右侧的内容
|
||||
value: { |
||||
type: [String, Number], |
||||
default: defprops.cell.value |
||||
}, |
||||
// 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
|
||||
icon: { |
||||
type: String, |
||||
default: defprops.cell.icon |
||||
}, |
||||
// 是否禁用cell
|
||||
disabled: { |
||||
type: Boolean, |
||||
default: defprops.cell.disabled |
||||
}, |
||||
// 是否显示下边框
|
||||
border: { |
||||
type: Boolean, |
||||
default: defprops.cell.border |
||||
}, |
||||
// 内容是否垂直居中(主要是针对右侧的value部分)
|
||||
center: { |
||||
type: Boolean, |
||||
default: defprops.cell.center |
||||
}, |
||||
// 点击后跳转的URL地址
|
||||
url: { |
||||
type: String, |
||||
default: defprops.cell.url |
||||
}, |
||||
// 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作
|
||||
linkType: { |
||||
type: String, |
||||
default: defprops.cell.linkType |
||||
}, |
||||
// 是否开启点击反馈(表现为点击时加上灰色背景)
|
||||
clickable: { |
||||
type: Boolean, |
||||
default: defprops.cell.clickable |
||||
}, |
||||
// 是否展示右侧箭头并开启点击反馈
|
||||
isLink: { |
||||
type: Boolean, |
||||
default: defprops.cell.isLink |
||||
}, |
||||
// 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
|
||||
required: { |
||||
type: Boolean, |
||||
default: defprops.cell.required |
||||
}, |
||||
// 右侧的图标箭头
|
||||
rightIcon: { |
||||
type: String, |
||||
default: defprops.cell.rightIcon |
||||
}, |
||||
// 右侧箭头的方向,可选值为:left,up,down
|
||||
arrowDirection: { |
||||
type: String, |
||||
default: defprops.cell.arrowDirection |
||||
}, |
||||
// 左侧图标样式
|
||||
iconStyle: { |
||||
type: [Object, String], |
||||
default: () => { |
||||
return uni.$u.props.cell.iconStyle |
||||
} |
||||
}, |
||||
// 右侧箭头图标的样式
|
||||
rightIconStyle: { |
||||
type: [Object, String], |
||||
default: () => { |
||||
return uni.$u.props.cell.rightIconStyle |
||||
} |
||||
}, |
||||
// 标题的样式
|
||||
titleStyle: { |
||||
type: [Object, String], |
||||
default: () => { |
||||
return uni.$u.props.cell.titleStyle |
||||
} |
||||
}, |
||||
// 单位元的大小,可选值为large
|
||||
size: { |
||||
type: String, |
||||
default: defprops.cell.size |
||||
}, |
||||
// 点击cell是否阻止事件传播
|
||||
stop: { |
||||
type: Boolean, |
||||
default: defprops.cell.stop |
||||
}, |
||||
// 标识符,cell被点击时返回
|
||||
name: { |
||||
type: [Number, String], |
||||
default: defprops.cell.name |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,234 @@ |
||||
<template> |
||||
<view class="u-cell" :class="[customClass]" :style="[$u.addStyle(customStyle)]" |
||||
:hover-class="(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''" :hover-stay-time="250" |
||||
@tap="clickHandler"> |
||||
<view class="u-cell__body" :class="[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']"> |
||||
<view class="u-cell__body__content"> |
||||
<view class="u-cell__left-icon-wrap"> |
||||
<slot name="icon"> |
||||
<u-icon v-if="icon" :name="icon" |
||||
:custom-style="iconStyle" |
||||
:size="size === 'large' ? 22 : 18"></u-icon> |
||||
</slot> |
||||
</view> |
||||
<view class="u-cell__title"> |
||||
<slot name="title"> |
||||
<text v-if="title" class="u-cell__title-text" :style="[titleTextStyle]" |
||||
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text> |
||||
</slot> |
||||
<slot name="label"> |
||||
<text class="u-cell__label" v-if="label" |
||||
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text> |
||||
</slot> |
||||
</view> |
||||
</view> |
||||
<slot name="value"> |
||||
<text class="u-cell__value" |
||||
:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']" |
||||
v-if="!$u.test.empty(value)">{{ value }}</text> |
||||
</slot> |
||||
<view class="u-cell__right-icon-wrap" |
||||
:class="[`u-cell__right-icon-wrap--${arrowDirection}`]"> |
||||
<slot name="right-icon"> |
||||
<u-icon v-if="isLink" :name="rightIcon" :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'" |
||||
:size="size === 'large' ? 18 : 16"></u-icon> |
||||
</slot> |
||||
</view> |
||||
</view> |
||||
<u-line v-if="border"></u-line> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* cell 单元格 |
||||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。 |
||||
* @tutorial https://uviewui.com/components/cell.html |
||||
* @property {String | Number} title 标题 |
||||
* @property {String | Number} label 标题下方的描述信息 |
||||
* @property {String | Number} value 右侧的内容 |
||||
* @property {String} icon 左侧图标名称,或者图片链接(本地文件建议使用绝对地址) |
||||
* @property {Boolean} disabled 是否禁用cell |
||||
* @property {Boolean} border 是否显示下边框 (默认 true ) |
||||
* @property {Boolean} center 内容是否垂直居中(主要是针对右侧的value部分) (默认 false ) |
||||
* @property {String} url 点击后跳转的URL地址 |
||||
* @property {String} linkType 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 (默认 'navigateTo' ) |
||||
* @property {Boolean} clickable 是否开启点击反馈(表现为点击时加上灰色背景) (默认 false ) |
||||
* @property {Boolean} isLink 是否展示右侧箭头并开启点击反馈 (默认 false ) |
||||
* @property {Boolean} required 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) (默认 false ) |
||||
* @property {String} rightIcon 右侧的图标箭头 (默认 'arrow-right') |
||||
* @property {String} arrowDirection 右侧箭头的方向,可选值为:left,up,down |
||||
* @property {Object | String} rightIconStyle 右侧箭头图标的样式 |
||||
* @property {Object | String} titleStyle 标题的样式 |
||||
* @property {Object | String} iconStyle 左侧图标样式 |
||||
* @property {String} size 单位元的大小,可选值为 large,normal,mini |
||||
* @property {Boolean} stop 点击cell是否阻止事件传播 (默认 true ) |
||||
* @property {Object} customStyle 定义需要用到的外部样式 |
||||
* |
||||
* @event {Function} click 点击cell列表时触发 |
||||
* @example 该组件需要搭配cell-group组件使用,见官方文档示例 |
||||
*/ |
||||
export default { |
||||
name: 'u-cell', |
||||
data() { |
||||
return { |
||||
|
||||
} |
||||
}, |
||||
mixins: [mpMixin, mixin, props], |
||||
computed: { |
||||
titleTextStyle() { |
||||
return uni.$u.addStyle(this.titleStyle) |
||||
} |
||||
}, |
||||
emits: ['click'], |
||||
methods: { |
||||
// 点击cell |
||||
clickHandler(e) { |
||||
if (this.disabled) return |
||||
this.$emit('click', { |
||||
name: this.name |
||||
}) |
||||
// 如果配置了url(此props参数通过mixin引入)参数,跳转页面 |
||||
this.openPage() |
||||
// 是否阻止事件传播 |
||||
this.stop && this.preventEvent(e) |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
$u-cell-padding: 10px 15px !default; |
||||
$u-cell-font-size: 15px !default; |
||||
$u-cell-line-height: 24px !default; |
||||
$u-cell-color: $u-main-color !default; |
||||
$u-cell-icon-size: 16px !default; |
||||
$u-cell-title-font-size: 15px !default; |
||||
$u-cell-title-line-height: 22px !default; |
||||
$u-cell-title-color: $u-main-color !default; |
||||
$u-cell-label-font-size: 12px !default; |
||||
$u-cell-label-color: $u-tips-color !default; |
||||
$u-cell-label-line-height: 18px !default; |
||||
$u-cell-value-font-size: 14px !default; |
||||
$u-cell-value-color: $u-content-color !default; |
||||
$u-cell-clickable-color: $u-bg-color !default; |
||||
$u-cell-disabled-color: #c8c9cc !default; |
||||
$u-cell-padding-top-large: 13px !default; |
||||
$u-cell-padding-bottom-large: 13px !default; |
||||
$u-cell-value-font-size-large: 15px !default; |
||||
$u-cell-label-font-size-large: 14px !default; |
||||
$u-cell-title-font-size-large: 16px !default; |
||||
$u-cell-left-icon-wrap-margin-right: 4px !default; |
||||
$u-cell-right-icon-wrap-margin-left: 4px !default; |
||||
$u-cell-title-flex:1 !default; |
||||
$u-cell-label-margin-top:5px !default; |
||||
|
||||
|
||||
.u-cell { |
||||
&__body { |
||||
@include flex(); |
||||
/* #ifndef APP-NVUE */ |
||||
box-sizing: border-box; |
||||
/* #endif */ |
||||
padding: $u-cell-padding; |
||||
font-size: $u-cell-font-size; |
||||
color: $u-cell-color; |
||||
// line-height: $u-cell-line-height; |
||||
align-items: center; |
||||
|
||||
&__content { |
||||
@include flex(row); |
||||
align-items: center; |
||||
flex: 1; |
||||
} |
||||
|
||||
&--large { |
||||
padding-top: $u-cell-padding-top-large; |
||||
padding-bottom: $u-cell-padding-bottom-large; |
||||
} |
||||
} |
||||
|
||||
&__left-icon-wrap, |
||||
&__right-icon-wrap { |
||||
@include flex(); |
||||
align-items: center; |
||||
// height: $u-cell-line-height; |
||||
font-size: $u-cell-icon-size; |
||||
} |
||||
|
||||
&__left-icon-wrap { |
||||
margin-right: $u-cell-left-icon-wrap-margin-right; |
||||
} |
||||
|
||||
&__right-icon-wrap { |
||||
margin-left: $u-cell-right-icon-wrap-margin-left; |
||||
transition: transform 0.3s; |
||||
|
||||
&--up { |
||||
transform: rotate(-90deg); |
||||
} |
||||
|
||||
&--down { |
||||
transform: rotate(90deg); |
||||
} |
||||
} |
||||
|
||||
&__title { |
||||
flex: $u-cell-title-flex; |
||||
|
||||
&-text { |
||||
font-size: $u-cell-title-font-size; |
||||
line-height: $u-cell-title-line-height; |
||||
color: $u-cell-title-color; |
||||
|
||||
&--large { |
||||
font-size: $u-cell-title-font-size-large; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
&__label { |
||||
margin-top: $u-cell-label-margin-top; |
||||
font-size: $u-cell-label-font-size; |
||||
color: $u-cell-label-color; |
||||
line-height: $u-cell-label-line-height; |
||||
|
||||
&--large { |
||||
font-size: $u-cell-label-font-size-large; |
||||
} |
||||
} |
||||
|
||||
&__value { |
||||
text-align: right; |
||||
font-size: $u-cell-value-font-size; |
||||
line-height: $u-cell-line-height; |
||||
color: $u-cell-value-color; |
||||
|
||||
&--large { |
||||
font-size: $u-cell-value-font-size-large; |
||||
} |
||||
} |
||||
|
||||
&--clickable { |
||||
background-color: $u-cell-clickable-color; |
||||
} |
||||
|
||||
&--disabled { |
||||
color: $u-cell-disabled-color; |
||||
/* #ifndef APP-NVUE */ |
||||
cursor: not-allowed; |
||||
/* #endif */ |
||||
} |
||||
|
||||
&--center { |
||||
align-items: center; |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,92 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 标识符
|
||||
name: { |
||||
type: String, |
||||
default: defprops.checkboxGroup.name |
||||
}, |
||||
// #ifdef VUE3
|
||||
// 绑定的值
|
||||
modelValue: { |
||||
type: Array, |
||||
default: defprops.checkboxGroup.value |
||||
}, |
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
// 绑定的值
|
||||
value: { |
||||
type: Array, |
||||
default: defprops.checkboxGroup.value |
||||
}, |
||||
// #endif
|
||||
// 形状,circle-圆形,square-方形
|
||||
shape: { |
||||
type: String, |
||||
default: defprops.checkboxGroup.shape |
||||
}, |
||||
// 是否禁用全部checkbox
|
||||
disabled: { |
||||
type: Boolean, |
||||
default: defprops.checkboxGroup.disabled |
||||
}, |
||||
|
||||
// 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
|
||||
activeColor: { |
||||
type: String, |
||||
default: defprops.checkboxGroup.activeColor |
||||
}, |
||||
// 未选中的颜色
|
||||
inactiveColor: { |
||||
type: String, |
||||
default: defprops.checkboxGroup.inactiveColor |
||||
}, |
||||
|
||||
// 整个组件的尺寸,默认px
|
||||
size: { |
||||
type: [String, Number], |
||||
default: defprops.checkboxGroup.size |
||||
}, |
||||
// 布局方式,row-横向,column-纵向
|
||||
placement: { |
||||
type: String, |
||||
default: defprops.checkboxGroup.placement |
||||
}, |
||||
// label的字体大小,px单位
|
||||
labelSize: { |
||||
type: [String, Number], |
||||
default: defprops.checkboxGroup.labelSize |
||||
}, |
||||
// label的字体颜色
|
||||
labelColor: { |
||||
type: [String], |
||||
default: defprops.checkboxGroup.labelColor |
||||
}, |
||||
// 是否禁止点击文本操作
|
||||
labelDisabled: { |
||||
type: Boolean, |
||||
default: defprops.checkboxGroup.labelDisabled |
||||
}, |
||||
// 图标颜色
|
||||
iconColor: { |
||||
type: String, |
||||
default: defprops.checkboxGroup.iconColor |
||||
}, |
||||
// 图标的大小,单位px
|
||||
iconSize: { |
||||
type: [String, Number], |
||||
default: defprops.checkboxGroup.iconSize |
||||
}, |
||||
// 勾选图标的对齐方式,left-左边,right-右边
|
||||
iconPlacement: { |
||||
type: String, |
||||
default: defprops.checkboxGroup.iconPlacement |
||||
}, |
||||
// 竖向配列时,是否显示下划线
|
||||
borderBottom: { |
||||
type: Boolean, |
||||
default: defprops.checkboxGroup.borderBottom |
||||
} |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,133 @@ |
||||
<template> |
||||
<view |
||||
class="u-checkbox-group" |
||||
:class="bemClass" |
||||
> |
||||
<slot></slot> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* checkboxGroup 复选框组 |
||||
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/checkbox.html |
||||
* @property {String} name 标识符 |
||||
* @property {Array} value 绑定的值 |
||||
* @property {String} shape 形状,circle-圆形,square-方形 (默认 'square' ) |
||||
* @property {Boolean} disabled 是否禁用全部checkbox (默认 false ) |
||||
* @property {String} activeColor 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 (默认 '#2979ff' ) |
||||
* @property {String} inactiveColor 未选中的颜色 (默认 '#c8c9cc' ) |
||||
* @property {String | Number} size 整个组件的尺寸 单位px (默认 18 ) |
||||
* @property {String} placement 布局方式,row-横向,column-纵向 (默认 'row' ) |
||||
* @property {String | Number} labelSize label的字体大小,px单位 (默认 14 ) |
||||
* @property {String} labelColor label的字体颜色 (默认 '#303133' ) |
||||
* @property {Boolean} labelDisabled 是否禁止点击文本操作 (默认 false ) |
||||
* @property {String} iconColor 图标颜色 (默认 '#ffffff' ) |
||||
* @property {String | Number} iconSize 图标的大小,单位px (默认 12 ) |
||||
* @property {String} iconPlacement 勾选图标的对齐方式,left-左边,right-右边 (默认 'left' ) |
||||
* @property {Boolean} borderBottom placement为row时,是否显示下边框 (默认 false ) |
||||
* @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象 |
||||
* @event {Function} input 修改通过v-model绑定的值时触发,回调为一个对象 |
||||
* @example <u-checkbox-group></u-checkbox-group> |
||||
*/ |
||||
export default { |
||||
name: 'u-checkbox-group', |
||||
mixins: [mpMixin, mixin,props], |
||||
computed: { |
||||
// 这里computed的变量,都是子组件u-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化 |
||||
// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-checkbox-group) |
||||
// 拉取父组件新的变化后的参数 |
||||
parentData() { |
||||
return [ |
||||
// #ifdef VUE2 |
||||
this.value, |
||||
// #endif |
||||
// #ifdef VUE3 |
||||
this.modelValue, |
||||
// #endif |
||||
this.disabled, |
||||
this.inactiveColor, |
||||
this.activeColor, |
||||
this.size, |
||||
this.labelDisabled, |
||||
this.shape, |
||||
this.iconSize, |
||||
this.borderBottom, |
||||
this.placement, |
||||
]; |
||||
}, |
||||
bemClass() { |
||||
// this.bem为一个computed变量,在mixin中 |
||||
return this.bem('checkbox-group', ['placement']) |
||||
}, |
||||
}, |
||||
watch: { |
||||
// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件 |
||||
parentData: { |
||||
handler() { |
||||
if (this.children.length) { |
||||
this.children.map((child) => { |
||||
// 判断子组件(u-checkbox)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值) |
||||
typeof child.init === "function" && child.init(); |
||||
}); |
||||
} |
||||
}, |
||||
deep: true, |
||||
}, |
||||
}, |
||||
data() { |
||||
return { |
||||
|
||||
} |
||||
}, |
||||
created() { |
||||
this.children = [] |
||||
}, |
||||
// #ifdef VUE3 |
||||
emits: ['update:modelValue', 'change'], |
||||
// #endif |
||||
methods: { |
||||
// 将其他的checkbox设置为未选中的状态 |
||||
unCheckedOther(childInstance) { |
||||
const values = [] |
||||
this.children.map(child => { |
||||
// 将被选中的checkbox,放到数组中返回 |
||||
if (child.isChecked) { |
||||
values.push(child.name) |
||||
} |
||||
}) |
||||
// 发出事件 |
||||
this.$emit('change', values) |
||||
// 修改通过v-model绑定的值 |
||||
// #ifdef VUE3 |
||||
this.$emit("update:modelValue", values); |
||||
// #endif |
||||
// #ifdef VUE2 |
||||
this.$emit("input", values); |
||||
// #endif |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
.u-checkbox-group { |
||||
|
||||
&--row { |
||||
/* #ifndef APP-NVUE */ |
||||
display: flex; |
||||
/* #endif */ |
||||
flex-flow: row wrap; |
||||
} |
||||
|
||||
&--column { |
||||
@include flex(column); |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,70 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// checkbox的名称
|
||||
name: { |
||||
type: [String, Number, Boolean], |
||||
default: defprops.checkbox.name |
||||
}, |
||||
// 形状,square为方形,circle为圆型
|
||||
shape: { |
||||
type: String, |
||||
default: defprops.checkbox.shape |
||||
}, |
||||
// 整体的大小
|
||||
size: { |
||||
type: [String, Number], |
||||
default: defprops.checkbox.size |
||||
}, |
||||
// 是否默认选中
|
||||
checked: { |
||||
type: Boolean, |
||||
default: defprops.checkbox.checked |
||||
}, |
||||
// 是否禁用
|
||||
disabled: { |
||||
type: [String, Boolean], |
||||
default: defprops.checkbox.disabled |
||||
}, |
||||
// 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
|
||||
activeColor: { |
||||
type: String, |
||||
default: defprops.checkbox.activeColor |
||||
}, |
||||
// 未选中的颜色
|
||||
inactiveColor: { |
||||
type: String, |
||||
default: defprops.checkbox.inactiveColor |
||||
}, |
||||
// 图标的大小,单位px
|
||||
iconSize: { |
||||
type: [String, Number], |
||||
default: defprops.checkbox.iconSize |
||||
}, |
||||
// 图标颜色
|
||||
iconColor: { |
||||
type: String, |
||||
default: defprops.checkbox.iconColor |
||||
}, |
||||
// label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
|
||||
label: { |
||||
type: [String, Number], |
||||
default: defprops.checkbox.label |
||||
}, |
||||
// label的字体大小,px单位
|
||||
labelSize: { |
||||
type: [String, Number], |
||||
default: defprops.checkbox.labelSize |
||||
}, |
||||
// label的颜色
|
||||
labelColor: { |
||||
type: String, |
||||
default: defprops.checkbox.labelColor |
||||
}, |
||||
// 是否禁止点击提示语选中复选框
|
||||
labelDisabled: { |
||||
type: [String, Boolean], |
||||
default: defprops.checkbox.labelDisabled |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,359 @@ |
||||
<template> |
||||
<view |
||||
class="u-checkbox" |
||||
:style="[checkboxStyle]" |
||||
@tap.stop="wrapperClickHandler" |
||||
:class="[`u-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']" |
||||
> |
||||
<view |
||||
class="u-checkbox__icon-wrap" |
||||
@tap.stop="iconClickHandler" |
||||
:class="iconClasses" |
||||
:style="[iconWrapStyle]" |
||||
> |
||||
<slot name="icon"> |
||||
<u-icon |
||||
class="u-checkbox__icon-wrap__icon" |
||||
name="checkbox-mark" |
||||
:size="elIconSize" |
||||
:color="elIconColor" |
||||
/> |
||||
</slot> |
||||
</view> |
||||
<text |
||||
@tap.stop="labelClickHandler" |
||||
:style="{ |
||||
color: elDisabled ? elInactiveColor : elLabelColor, |
||||
fontSize: elLabelSize, |
||||
lineHeight: elLabelSize |
||||
}" |
||||
>{{label}}</text> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* checkbox 复选框 |
||||
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便 |
||||
* @tutorial https://uviewui.com/components/checkbox.html |
||||
* @property {String | Number | Boolean} name checkbox组件的标示符 |
||||
* @property {String} shape 形状,square为方形,circle为圆型 |
||||
* @property {String | Number} size 整体的大小 |
||||
* @property {Boolean} checked 是否默认选中 |
||||
* @property {String | Boolean} disabled 是否禁用 |
||||
* @property {String} activeColor 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 |
||||
* @property {String} inactiveColor 未选中的颜色 |
||||
* @property {String | Number} iconSize 图标的大小,单位px |
||||
* @property {String} iconColor 图标颜色 |
||||
* @property {String | Number} label label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式 |
||||
* @property {String} labelColor label的颜色 |
||||
* @property {String | Number} labelSize label的字体大小,px单位 |
||||
* @property {String | Boolean} labelDisabled 是否禁止点击提示语选中复选框 |
||||
* @property {Object} customStyle 定义需要用到的外部样式 |
||||
* |
||||
* @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象 |
||||
* @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox> |
||||
*/ |
||||
export default { |
||||
name: "u-checkbox", |
||||
mixins: [mpMixin, mixin, props], |
||||
data() { |
||||
return { |
||||
isChecked: false, |
||||
// 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式 |
||||
// 故只能使用如此方法 |
||||
parentData: { |
||||
iconSize: 12, |
||||
labelDisabled: null, |
||||
disabled: null, |
||||
shape: 'square', |
||||
activeColor: null, |
||||
inactiveColor: null, |
||||
size: 18, |
||||
// #ifdef VUE2 |
||||
value: null, |
||||
// #endif |
||||
// #ifdef VUE3 |
||||
modelValue: null, |
||||
// #endif |
||||
iconColor: null, |
||||
placement: 'row', |
||||
borderBottom: false, |
||||
iconPlacement: 'left' |
||||
} |
||||
} |
||||
}, |
||||
computed: { |
||||
// 是否禁用,如果父组件u-raios-group禁用的话,将会忽略子组件的配置 |
||||
elDisabled() { |
||||
return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false; |
||||
}, |
||||
// 是否禁用label点击 |
||||
elLabelDisabled() { |
||||
return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled : |
||||
false; |
||||
}, |
||||
// 组件尺寸,对应size的值,默认值为21px |
||||
elSize() { |
||||
return this.size ? this.size : (this.parentData.size ? this.parentData.size : 21); |
||||
}, |
||||
// 组件的勾选图标的尺寸,默认12px |
||||
elIconSize() { |
||||
return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12); |
||||
}, |
||||
// 组件选中激活时的颜色 |
||||
elActiveColor() { |
||||
return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff'); |
||||
}, |
||||
// 组件选未中激活时的颜色 |
||||
elInactiveColor() { |
||||
return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor : |
||||
'#c8c9cc'); |
||||
}, |
||||
// label的颜色 |
||||
elLabelColor() { |
||||
return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266') |
||||
}, |
||||
// 组件的形状 |
||||
elShape() { |
||||
return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle'); |
||||
}, |
||||
// label大小 |
||||
elLabelSize() { |
||||
return uni.$u.addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize : |
||||
'15')) |
||||
}, |
||||
elIconColor() { |
||||
const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor : |
||||
'#ffffff'); |
||||
// 图标的颜色 |
||||
if (this.elDisabled) { |
||||
// disabled状态下,已勾选的checkbox图标改为elInactiveColor |
||||
return this.isChecked ? this.elInactiveColor : 'transparent' |
||||
} else { |
||||
return this.isChecked ? iconColor : 'transparent' |
||||
} |
||||
}, |
||||
iconClasses() { |
||||
let classes = [] |
||||
// 组件的形状 |
||||
classes.push('u-checkbox__icon-wrap--' + this.elShape) |
||||
if (this.elDisabled) { |
||||
classes.push('u-checkbox__icon-wrap--disabled') |
||||
} |
||||
if (this.isChecked && this.elDisabled) { |
||||
classes.push('u-checkbox__icon-wrap--disabled--checked') |
||||
} |
||||
// 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效 |
||||
// #ifdef MP-ALIPAY || MP-TOUTIAO |
||||
classes = classes.join(' ') |
||||
// #endif |
||||
return classes |
||||
}, |
||||
iconWrapStyle() { |
||||
// checkbox的整体样式 |
||||
const style = {} |
||||
style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff' |
||||
style.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor |
||||
style.width = uni.$u.addUnit(this.elSize) |
||||
style.height = uni.$u.addUnit(this.elSize) |
||||
// 如果是图标在右边的话,移除它的右边距 |
||||
if (this.parentData.iconPlacement === 'right') { |
||||
style.marginRight = 0 |
||||
} |
||||
return style |
||||
}, |
||||
checkboxStyle() { |
||||
const style = {} |
||||
if (this.parentData.borderBottom && this.parentData.placement === 'row') { |
||||
uni.$u.error('检测到您将borderBottom设置为true,需要同时将u-checkbox-group的placement设置为column才有效') |
||||
} |
||||
// 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔 |
||||
if (this.parentData.borderBottom && this.parentData.placement === 'column') { |
||||
style.paddingBottom = '8px' |
||||
} |
||||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) |
||||
} |
||||
}, |
||||
mounted() { |
||||
this.init() |
||||
}, |
||||
methods: { |
||||
init() { |
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用 |
||||
this.updateParentData() |
||||
if (!this.parent) { |
||||
uni.$u.error('u-checkbox必须搭配u-checkbox-group组件使用') |
||||
} |
||||
// #ifdef VUE2 |
||||
const value = this.parentData.value |
||||
// #endif |
||||
// #ifdef VUE3 |
||||
const value = this.parentData.modelValue |
||||
// #endif |
||||
// 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断 |
||||
if (this.checked) { |
||||
this.isChecked = true |
||||
} else if (uni.$u.test.array(value)) { |
||||
// 查找数组是是否存在this.name元素值 |
||||
this.isChecked = value.some(item => { |
||||
return item === this.name |
||||
}) |
||||
} |
||||
}, |
||||
updateParentData() { |
||||
this.getParentData('u-checkbox-group') |
||||
}, |
||||
// 横向两端排列时,点击组件即可触发选中事件 |
||||
wrapperClickHandler(e) { |
||||
this.parentData.iconPlacement === 'right' && this.iconClickHandler(e) |
||||
}, |
||||
// 点击图标 |
||||
iconClickHandler(e) { |
||||
this.preventEvent(e) |
||||
// 如果整体被禁用,不允许被点击 |
||||
if (!this.elDisabled) { |
||||
this.setRadioCheckedStatus() |
||||
} |
||||
}, |
||||
// 点击label |
||||
labelClickHandler(e) { |
||||
this.preventEvent(e) |
||||
// 如果按钮整体被禁用或者label被禁用,则不允许点击文字修改状态 |
||||
if (!this.elLabelDisabled && !this.elDisabled) { |
||||
this.setRadioCheckedStatus() |
||||
} |
||||
}, |
||||
emitEvent() { |
||||
this.$emit('change', this.isChecked) |
||||
// 尝试调用u-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时 |
||||
this.$nextTick(() => { |
||||
uni.$u.formValidate(this, 'change') |
||||
}) |
||||
}, |
||||
// 改变组件选中状态 |
||||
// 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有u-checkbox实例 |
||||
// 将本组件外的其他u-checkbox的checked都设置为false(都被取消选中状态),因而只剩下一个为选中状态 |
||||
setRadioCheckedStatus() { |
||||
// 将本组件标记为与原来相反的状态 |
||||
this.isChecked = !this.isChecked |
||||
this.emitEvent() |
||||
typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this) |
||||
} |
||||
}, |
||||
watch:{ |
||||
checked(){ |
||||
this.isChecked = this.checked |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
$u-checkbox-icon-wrap-margin-right:6px !default; |
||||
$u-checkbox-icon-wrap-font-size:6px !default; |
||||
$u-checkbox-icon-wrap-border-width:1px !default; |
||||
$u-checkbox-icon-wrap-border-color:#c8c9cc !default; |
||||
$u-checkbox-icon-wrap-icon-line-height:0 !default; |
||||
$u-checkbox-icon-wrap-circle-border-radius:100% !default; |
||||
$u-checkbox-icon-wrap-square-border-radius:3px !default; |
||||
$u-checkbox-icon-wrap-checked-color:#fff !default; |
||||
$u-checkbox-icon-wrap-checked-background-color:red !default; |
||||
$u-checkbox-icon-wrap-checked-border-color:#2979ff !default; |
||||
$u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default; |
||||
$u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default; |
||||
$u-checkbox-label-margin-left:5px !default; |
||||
$u-checkbox-label-margin-right:12px !default; |
||||
$u-checkbox-label-color:$u-content-color !default; |
||||
$u-checkbox-label-font-size:15px !default; |
||||
$u-checkbox-label-disabled-color:#c8c9cc !default; |
||||
|
||||
.u-checkbox { |
||||
/* #ifndef APP-NVUE */ |
||||
@include flex(row); |
||||
/* #endif */ |
||||
overflow: hidden; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
margin-bottom: 5px; |
||||
margin-top: 5px; |
||||
|
||||
&-label--left { |
||||
flex-direction: row |
||||
} |
||||
|
||||
&-label--right { |
||||
flex-direction: row-reverse; |
||||
justify-content: space-between |
||||
} |
||||
|
||||
&__icon-wrap { |
||||
/* #ifndef APP-NVUE */ |
||||
box-sizing: border-box; |
||||
// nvue下,border-color过渡有问题 |
||||
transition-property: border-color, background-color, color; |
||||
transition-duration: 0.2s; |
||||
/* #endif */ |
||||
color: $u-content-color; |
||||
@include flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
color: transparent; |
||||
text-align: center; |
||||
margin-right: $u-checkbox-icon-wrap-margin-right; |
||||
|
||||
font-size: $u-checkbox-icon-wrap-font-size; |
||||
border-width: $u-checkbox-icon-wrap-border-width; |
||||
border-color: $u-checkbox-icon-wrap-border-color; |
||||
border-style: solid; |
||||
|
||||
/* #ifdef MP-TOUTIAO */ |
||||
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下 |
||||
&__icon { |
||||
line-height: $u-checkbox-icon-wrap-icon-line-height; |
||||
} |
||||
|
||||
/* #endif */ |
||||
|
||||
&--circle { |
||||
border-radius: $u-checkbox-icon-wrap-circle-border-radius; |
||||
} |
||||
|
||||
&--square { |
||||
border-radius: $u-checkbox-icon-wrap-square-border-radius; |
||||
} |
||||
|
||||
&--checked { |
||||
color: $u-checkbox-icon-wrap-checked-color; |
||||
background-color: $u-checkbox-icon-wrap-checked-background-color; |
||||
border-color: $u-checkbox-icon-wrap-checked-border-color; |
||||
} |
||||
|
||||
&--disabled { |
||||
background-color: $u-checkbox-icon-wrap-disabled-background-color !important; |
||||
} |
||||
|
||||
&--disabled--checked { |
||||
color: $u-checkbox-icon-wrap-disabled-checked-color !important; |
||||
} |
||||
} |
||||
|
||||
&__label { |
||||
/* #ifndef APP-NVUE */ |
||||
word-wrap: break-word; |
||||
/* #endif */ |
||||
margin-left: $u-checkbox-label-margin-left; |
||||
margin-right: $u-checkbox-label-margin-right; |
||||
color: $u-checkbox-label-color; |
||||
font-size: $u-checkbox-label-font-size; |
||||
|
||||
&--disabled { |
||||
color: $u-checkbox-label-disabled-color; |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,9 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
percentage: { |
||||
type: [String, Number], |
||||
default: defprops.circleProgress.percentage |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,200 @@ |
||||
<template> |
||||
<view class="u-circle-progress"> |
||||
<view class="u-circle-progress__left"> |
||||
<view |
||||
class="u-circle-progress__left__circle" |
||||
:style="[leftSyle]" |
||||
ref="left-circle" |
||||
> |
||||
|
||||
</view> |
||||
</view> |
||||
<view |
||||
class="u-circle-progress__right" |
||||
> |
||||
<view |
||||
class="u-circle-progress__right__circle" |
||||
ref="right-circle" |
||||
:style="[rightSyle]" |
||||
> |
||||
|
||||
</view> |
||||
</view> |
||||
<view class="u-circle-progress__circle"> |
||||
|
||||
</view> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
// #ifdef APP-NVUE |
||||
const animation = uni.requireNativePlugin('animation') |
||||
// #endif |
||||
/** |
||||
* CircleProgress 圆形进度条 TODO: 待完善 |
||||
* @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度环。 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/circleProgress.html |
||||
* @property {String | Number} percentage 圆环进度百分比值,为数值类型,0-100 (默认 30 ) |
||||
* @example |
||||
*/ |
||||
export default { |
||||
name: 'u-circle-progress', |
||||
mixins: [mpMixin, mixin, props], |
||||
data() { |
||||
return { |
||||
leftBorderColor: 'rgb(200, 200, 200)', |
||||
rightBorderColor: 'rgb(200, 200, 200)', |
||||
} |
||||
}, |
||||
computed: { |
||||
leftSyle() { |
||||
const style = {} |
||||
style.borderTopColor = this.leftBorderColor |
||||
style.borderRightColor = this.leftBorderColor |
||||
return style |
||||
}, |
||||
rightSyle() { |
||||
const style = {} |
||||
style.borderLeftColor = this.rightBorderColor |
||||
style.borderBottomColor = this.rightBorderColor |
||||
return style |
||||
} |
||||
}, |
||||
mounted() { |
||||
uni.$u.sleep().then(() => { |
||||
this.rightBorderColor = 'rgb(66, 185, 131)' |
||||
// this.init() |
||||
}) |
||||
}, |
||||
methods: { |
||||
init() { |
||||
animation.transition(this.$refs['right-circle'].ref, { |
||||
styles: { |
||||
transform: 'rotate(45deg)', |
||||
transformOrigin: 'center center' |
||||
}, |
||||
}, () => { |
||||
this.rightBorderColor = 'rgb(66, 185, 131)' |
||||
// animation.transition(this.$refs['right-circle'].ref, { |
||||
// styles: { |
||||
// transform: 'rotate(225deg)', |
||||
// transformOrigin: 'center center' |
||||
// }, |
||||
// duration: 3000, |
||||
// }, () => { |
||||
// animation.transition(this.$refs['left-circle'].ref, { |
||||
// styles: { |
||||
// transform: 'rotate(45deg)', |
||||
// transformOrigin: 'center center' |
||||
// }, |
||||
// }, () => { |
||||
// this.leftBorderColor = 'rgb(66, 185, 131)' |
||||
// animation.transition(this.$refs['left-circle'].ref, { |
||||
// styles: { |
||||
// transform: 'rotate(225deg)', |
||||
// transformOrigin: 'center center' |
||||
// }, |
||||
// duration: 1500, |
||||
// }, () => { |
||||
|
||||
// }) |
||||
// }) |
||||
// }) |
||||
}) |
||||
|
||||
} |
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
.u-circle-progress { |
||||
@include flex(row); |
||||
position: relative; |
||||
border-radius: 100px; |
||||
height: 100px; |
||||
width: 100px; |
||||
// transform: rotate(0deg); |
||||
// background-color: rgb(66, 185, 131); |
||||
background-color: rgb(200, 200, 200); |
||||
overflow: hidden; |
||||
justify-content: space-between; |
||||
|
||||
&__circle { |
||||
border-radius: 100px; |
||||
height: 90px; |
||||
width: 90px; |
||||
transform: translate(-50%, -50%); |
||||
background-color: rgb(255, 255, 255); |
||||
left: 50px; |
||||
top: 50px; |
||||
position: absolute; |
||||
} |
||||
|
||||
&__left { |
||||
position: absolute; |
||||
left: 0; |
||||
width: 50px; |
||||
height: 100px; |
||||
overflow: hidden; |
||||
box-sizing: border-box; |
||||
// background-color: rgb(66, 185, 131); |
||||
// background-color: rgb(200, 200, 200); |
||||
// transform-origin: left center; |
||||
|
||||
&__circle { |
||||
box-sizing: border-box; |
||||
// background-color: red; |
||||
border-left-color: transparent; |
||||
border-bottom-color: transparent; |
||||
border-top-left-radius: 50px; |
||||
border-top-right-radius: 50px; |
||||
border-bottom-right-radius: 50px; |
||||
// border-left-color: rgb(66, 185, 131); |
||||
// border-bottom-color: rgb(66, 185, 131); |
||||
border-top-color: rgb(66, 185, 131); |
||||
border-right-color: rgb(66, 185, 131); |
||||
border-width: 5px; |
||||
width: 100px; |
||||
height: 100px; |
||||
transform: rotate(225deg); |
||||
// border-radius: 100px; |
||||
} |
||||
} |
||||
|
||||
&__right { |
||||
position: absolute; |
||||
right: 0; |
||||
width: 50px; |
||||
height: 100px; |
||||
overflow: hidden; |
||||
|
||||
&__circle { |
||||
position: absolute; |
||||
right: 0; |
||||
box-sizing: border-box; |
||||
// background-color: red; |
||||
border-top-color: transparent; |
||||
border-right-color: transparent; |
||||
border-top-left-radius: 50px; |
||||
border-bottom-left-radius: 50px; |
||||
border-bottom-right-radius: 50px; |
||||
// border-left-color: rgb(66, 185, 131); |
||||
// border-bottom-color: rgb(66, 185, 131); |
||||
border-left-color: rgb(200, 200, 200); |
||||
border-bottom-color: rgb(200, 200, 200); |
||||
border-width: 5px; |
||||
width: 100px; |
||||
height: 100px; |
||||
transform: rotate(45deg); |
||||
transform-origin: center center; |
||||
// border-radius: 100px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
||||
@ -0,0 +1,80 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 键盘弹起时,是否自动上推页面
|
||||
adjustPosition: { |
||||
type: Boolean, |
||||
default: defprops.codeInput.adjustPosition |
||||
}, |
||||
// 最大输入长度
|
||||
maxlength: { |
||||
type: [String, Number], |
||||
default: defprops.codeInput.maxlength |
||||
}, |
||||
// 是否用圆点填充
|
||||
dot: { |
||||
type: Boolean, |
||||
default: defprops.codeInput.dot |
||||
}, |
||||
// 显示模式,box-盒子模式,line-底部横线模式
|
||||
mode: { |
||||
type: String, |
||||
default: defprops.codeInput.mode |
||||
}, |
||||
// 是否细边框
|
||||
hairline: { |
||||
type: Boolean, |
||||
default: defprops.codeInput.hairline |
||||
}, |
||||
// 字符间的距离
|
||||
space: { |
||||
type: [String, Number], |
||||
default: defprops.codeInput.space |
||||
}, |
||||
// 预置值
|
||||
value: { |
||||
type: [String, Number], |
||||
default: defprops.codeInput.value |
||||
}, |
||||
// 是否自动获取焦点
|
||||
focus: { |
||||
type: Boolean, |
||||
default: defprops.codeInput.focus |
||||
}, |
||||
// 字体是否加粗
|
||||
bold: { |
||||
type: Boolean, |
||||
default: defprops.codeInput.bold |
||||
}, |
||||
// 字体颜色
|
||||
color: { |
||||
type: String, |
||||
default: defprops.codeInput.color |
||||
}, |
||||
// 字体大小
|
||||
fontSize: { |
||||
type: [String, Number], |
||||
default: defprops.codeInput.fontSize |
||||
}, |
||||
// 输入框的大小,宽等于高
|
||||
size: { |
||||
type: [String, Number], |
||||
default: defprops.codeInput.size |
||||
}, |
||||
// 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true
|
||||
disabledKeyboard: { |
||||
type: Boolean, |
||||
default: defprops.codeInput.disabledKeyboard |
||||
}, |
||||
// 边框和线条颜色
|
||||
borderColor: { |
||||
type: String, |
||||
default: defprops.codeInput.borderColor |
||||
}, |
||||
// 是否禁止输入"."符号
|
||||
disabledDot: { |
||||
type: Boolean, |
||||
default: defprops.codeInput.disabledDot |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,254 @@ |
||||
<template> |
||||
<view class="u-code-input"> |
||||
<view |
||||
class="u-code-input__item" |
||||
:style="[itemStyle(index)]" |
||||
v-for="(item, index) in codeLength" |
||||
:key="index" |
||||
> |
||||
<view |
||||
class="u-code-input__item__dot" |
||||
v-if="dot && codeArray.length > index" |
||||
></view> |
||||
<text |
||||
v-else |
||||
:style="{ |
||||
fontSize: $u.addUnit(fontSize), |
||||
fontWeight: bold ? 'bold' : 'normal', |
||||
color: color |
||||
}" |
||||
>{{codeArray[index]}}</text> |
||||
<view |
||||
class="u-code-input__item__line" |
||||
v-if="mode === 'line'" |
||||
:style="[lineStyle]" |
||||
></view> |
||||
<!-- #ifndef APP-PLUS --> |
||||
<view v-if="isFocus && codeArray.length === index" :style="{backgroundColor: color}" class="u-code-input__item__cursor"></view> |
||||
<!-- #endif --> |
||||
</view> |
||||
<input |
||||
:disabled="disabledKeyboard" |
||||
type="number" |
||||
:focus="focus" |
||||
:value="inputValue" |
||||
:maxlength="maxlength" |
||||
:adjustPosition="adjustPosition" |
||||
class="u-code-input__input" |
||||
@input="inputHandler" |
||||
:style="{ |
||||
height: $u.addUnit(size) |
||||
}" |
||||
@focus="isFocus = true" |
||||
@blur="isFocus = false" |
||||
/> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* CodeInput 验证码输入 |
||||
* @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/codeInput.html |
||||
* @property {String | Number} maxlength 最大输入长度 (默认 6 ) |
||||
* @property {Boolean} dot 是否用圆点填充 (默认 false ) |
||||
* @property {String} mode 显示模式,box-盒子模式,line-底部横线模式 (默认 'box' ) |
||||
* @property {Boolean} hairline 是否细边框 (默认 false ) |
||||
* @property {String | Number} space 字符间的距离 (默认 10 ) |
||||
* @property {String | Number} value 预置值 |
||||
* @property {Boolean} focus 是否自动获取焦点 (默认 false ) |
||||
* @property {Boolean} bold 字体和输入横线是否加粗 (默认 false ) |
||||
* @property {String} color 字体颜色 (默认 '#606266' ) |
||||
* @property {String | Number} fontSize 字体大小,单位px (默认 18 ) |
||||
* @property {String | Number} size 输入框的大小,宽等于高 (默认 35 ) |
||||
* @property {Boolean} disabledKeyboard 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true (默认 false ) |
||||
* @property {String} borderColor 边框和线条颜色 (默认 '#c9cacc' ) |
||||
* @property {Boolean} disabledDot 是否禁止输入"."符号 (默认 true ) |
||||
* |
||||
* @event {Function} change 输入内容发生改变时触发,具体见上方说明 value:当前输入的值 |
||||
* @event {Function} finish 输入字符个数达maxlength值时触发,见上方说明 value:当前输入的值 |
||||
* @example <u-code-input v-model="value4" :focus="true"></u-code-input> |
||||
*/ |
||||
export default { |
||||
name: 'u-code-input', |
||||
mixins: [mpMixin, mixin, props], |
||||
data() { |
||||
return { |
||||
inputValue: '', |
||||
isFocus: this.focus |
||||
} |
||||
}, |
||||
watch: { |
||||
value: { |
||||
immediate: true, |
||||
handler(val) { |
||||
// 转为字符串,超出部分截掉 |
||||
this.inputValue = String(val).substring(0, this.maxlength) |
||||
} |
||||
}, |
||||
}, |
||||
computed: { |
||||
// 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for |
||||
codeLength() { |
||||
return new Array(Number(this.maxlength)) |
||||
}, |
||||
// 循环item的样式 |
||||
itemStyle() { |
||||
return index => { |
||||
const addUnit = uni.$u.addUnit |
||||
const style = { |
||||
width: addUnit(this.size), |
||||
height: addUnit(this.size) |
||||
} |
||||
// 盒子模式下,需要额外进行处理 |
||||
if (this.mode === 'box') { |
||||
// 设置盒子的边框,如果是细边框,则设置为0.5px宽度 |
||||
style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}` |
||||
// 如果盒子间距为0的话 |
||||
if (uni.$u.getPx(this.space) === 0) { |
||||
// 给第一和最后一个盒子设置圆角 |
||||
if (index === 0) { |
||||
style.borderTopLeftRadius = '3px' |
||||
style.borderBottomLeftRadius = '3px' |
||||
} |
||||
if (index === this.codeLength.length - 1) { |
||||
style.borderTopRightRadius = '3px' |
||||
style.borderBottomRightRadius = '3px' |
||||
} |
||||
// 最后一个盒子的右边框需要保留 |
||||
if (index !== this.codeLength.length - 1) { |
||||
style.borderRight = 'none' |
||||
} |
||||
} |
||||
} |
||||
if (index !== this.codeLength.length - 1) { |
||||
// 设置验证码字符之间的距离,通过margin-right设置,最后一个字符,无需右边框 |
||||
style.marginRight = addUnit(this.space) |
||||
} else { |
||||
// 最后一个盒子的有边框需要保留 |
||||
style.marginRight = 0 |
||||
} |
||||
|
||||
return style |
||||
} |
||||
}, |
||||
// 将输入的值,转为数组,给item历遍时,根据当前的索引显示数组的元素 |
||||
codeArray() { |
||||
return String(this.inputValue).split('') |
||||
}, |
||||
// 下划线模式下,横线的样式 |
||||
lineStyle() { |
||||
const style = {} |
||||
style.height = this.hairline ? '2px' : '4px' |
||||
style.width = uni.$u.addUnit(this.size) |
||||
// 线条模式下,背景色即为边框颜色 |
||||
style.backgroundColor = this.borderColor |
||||
return style |
||||
} |
||||
}, |
||||
methods: { |
||||
// 监听输入框的值发生变化 |
||||
inputHandler(e) { |
||||
const value = e.detail.value |
||||
this.inputValue = value |
||||
// 是否允许输入“.”符号 |
||||
if(this.disabledDot) { |
||||
this.$nextTick(() => { |
||||
this.inputValue = value.replace('.', '') |
||||
}) |
||||
} |
||||
// 未达到maxlength之前,发送change事件,达到后发送finish事件 |
||||
this.$emit('change', value) |
||||
// 修改通过v-model双向绑定的值 |
||||
this.$emit('input', value) |
||||
// 达到用户指定输入长度时,发出完成事件 |
||||
if (String(value).length >= Number(this.maxlength)) { |
||||
this.$emit('finish', value) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
$u-code-input-cursor-width: 1px; |
||||
$u-code-input-cursor-height: 40%; |
||||
$u-code-input-cursor-animation-duration: 1s; |
||||
$u-code-input-cursor-animation-name: u-cursor-flicker; |
||||
|
||||
.u-code-input { |
||||
@include flex; |
||||
position: relative; |
||||
overflow: hidden; |
||||
|
||||
&__item { |
||||
@include flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
position: relative; |
||||
|
||||
&__text { |
||||
font-size: 15px; |
||||
color: $u-content-color; |
||||
} |
||||
|
||||
&__dot { |
||||
width: 7px; |
||||
height: 7px; |
||||
border-radius: 100px; |
||||
background-color: $u-content-color; |
||||
} |
||||
|
||||
&__line { |
||||
position: absolute; |
||||
bottom: 0; |
||||
height: 4px; |
||||
border-radius: 100px; |
||||
width: 40px; |
||||
background-color: $u-content-color; |
||||
} |
||||
/* #ifndef APP-PLUS */ |
||||
&__cursor { |
||||
position: absolute; |
||||
top: 50%; |
||||
left: 50%; |
||||
transform: translate(-50%,-50%); |
||||
width: $u-code-input-cursor-width; |
||||
height: $u-code-input-cursor-height; |
||||
animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite; |
||||
} |
||||
/* #endif */ |
||||
|
||||
} |
||||
|
||||
&__input { |
||||
// 之所以需要input输入框,是因为有它才能唤起键盘 |
||||
// 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容 |
||||
position: absolute; |
||||
left: -750rpx; |
||||
width: 1500rpx; |
||||
top: 0; |
||||
background-color: transparent; |
||||
text-align: left; |
||||
} |
||||
} |
||||
|
||||
/* #ifndef APP-PLUS */ |
||||
@keyframes u-cursor-flicker { |
||||
0% { |
||||
opacity: 0; |
||||
} |
||||
50% { |
||||
opacity: 1; |
||||
} |
||||
100% { |
||||
opacity: 0; |
||||
} |
||||
} |
||||
/* #endif */ |
||||
|
||||
</style> |
||||
@ -0,0 +1,35 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 倒计时总秒数
|
||||
seconds: { |
||||
type: [String, Number], |
||||
default: defprops.code.seconds |
||||
}, |
||||
// 尚未开始时提示
|
||||
startText: { |
||||
type: String, |
||||
default: defprops.code.startText |
||||
}, |
||||
// 正在倒计时中的提示
|
||||
changeText: { |
||||
type: String, |
||||
default: defprops.code.changeText |
||||
}, |
||||
// 倒计时结束时的提示
|
||||
endText: { |
||||
type: String, |
||||
default: defprops.code.endText |
||||
}, |
||||
// 是否在H5刷新或各端返回再进入时继续倒计时
|
||||
keepRunning: { |
||||
type: Boolean, |
||||
default: defprops.code.keepRunning |
||||
}, |
||||
// 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
|
||||
uniqueKey: { |
||||
type: String, |
||||
default: defprops.code.uniqueKey |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,131 @@ |
||||
<template> |
||||
<view class="u-code"> |
||||
<!-- 此组件功能由js完成,无需写html逻辑 --> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* Code 验证码输入框 |
||||
* @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件 不提供界面显示,只提供提示语,由用户将提示语嵌入到具体的场景 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/code.html |
||||
* @property {String | Number} seconds 倒计时所需的秒数(默认 60 ) |
||||
* @property {String} startText 开始前的提示语,见官网说明(默认 '获取验证码' ) |
||||
* @property {String} changeText 倒计时期间的提示语,必须带有字母"x",见官网说明(默认 'X秒重新获取' ) |
||||
* @property {String} endText 倒计结束的提示语,见官网说明(默认 '重新获取' ) |
||||
* @property {Boolean} keepRunning 是否在H5刷新或各端返回再进入时继续倒计时( 默认false ) |
||||
* @property {String} uniqueKey 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了 |
||||
* |
||||
* @event {Function} change 倒计时期间,每秒触发一次 |
||||
* @event {Function} start 开始倒计时触发 |
||||
* @event {Function} end 结束倒计时触发 |
||||
* @example <u-code ref="uCode" @change="codeChange" seconds="20"></u-code> |
||||
*/ |
||||
export default { |
||||
name: "u-code", |
||||
mixins: [mpMixin, mixin,props], |
||||
data() { |
||||
return { |
||||
secNum: this.seconds, |
||||
timer: null, |
||||
canGetCode: true, // 是否可以执行验证码操作 |
||||
} |
||||
}, |
||||
mounted() { |
||||
this.checkKeepRunning() |
||||
}, |
||||
watch: { |
||||
seconds: { |
||||
immediate: true, |
||||
handler(n) { |
||||
this.secNum = n |
||||
} |
||||
} |
||||
}, |
||||
methods: { |
||||
checkKeepRunning() { |
||||
// 获取上一次退出页面(H5还包括刷新)时的时间戳,如果没有上次的保存,此值可能为空 |
||||
let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp')) |
||||
if(!lastTimestamp) return this.changeEvent(this.startText) |
||||
// 当前秒的时间戳 |
||||
let nowTimestamp = Math.floor((+ new Date()) / 1000) |
||||
// 判断当前的时间戳,是否小于上一次的本该按设定结束,却提前结束的时间戳 |
||||
if(this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) { |
||||
// 剩余尚未执行完的倒计秒数 |
||||
this.secNum = lastTimestamp - nowTimestamp |
||||
// 清除本地保存的变量 |
||||
uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp') |
||||
// 开始倒计时 |
||||
this.start() |
||||
} else { |
||||
// 如果不存在需要继续上一次的倒计时,执行正常的逻辑 |
||||
this.changeEvent(this.startText) |
||||
} |
||||
}, |
||||
// 开始倒计时 |
||||
start() { |
||||
// 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱 |
||||
if(this.timer) { |
||||
clearInterval(this.timer) |
||||
this.timer = null |
||||
} |
||||
this.$emit('start') |
||||
this.canGetCode = false |
||||
// 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示 |
||||
this.changeEvent(this.changeText.replace(/x|X/, this.secNum)) |
||||
this.setTimeToStorage() |
||||
this.timer = setInterval(() => { |
||||
if (--this.secNum) { |
||||
// 用当前倒计时的秒数替换提示字符串中的"x"字母 |
||||
this.changeEvent(this.changeText.replace(/x|X/, this.secNum)) |
||||
} else { |
||||
clearInterval(this.timer) |
||||
this.timer = null |
||||
this.changeEvent(this.endText) |
||||
this.secNum = this.seconds |
||||
this.$emit('end') |
||||
this.canGetCode = true |
||||
} |
||||
}, 1000) |
||||
}, |
||||
// 重置,可以让用户再次获取验证码 |
||||
reset() { |
||||
this.canGetCode = true |
||||
clearInterval(this.timer) |
||||
this.secNum = this.seconds |
||||
this.changeEvent(this.endText) |
||||
}, |
||||
changeEvent(text) { |
||||
this.$emit('change', text) |
||||
}, |
||||
// 保存时间戳,为了防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来 |
||||
setTimeToStorage() { |
||||
if(!this.keepRunning || !this.timer) return |
||||
// 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时 |
||||
// 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理 |
||||
if(this.secNum > 0 && this.secNum <= this.seconds) { |
||||
// 获取当前时间戳(+ new Date()为特殊写法),除以1000变成秒,再去除小数部分 |
||||
let nowTimestamp = Math.floor((+ new Date()) / 1000) |
||||
// 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数 |
||||
uni.setStorage({ |
||||
key: this.uniqueKey + '_$uCountDownTimestamp', |
||||
data: nowTimestamp + Number(this.secNum) |
||||
}) |
||||
} |
||||
} |
||||
}, |
||||
// 组件销毁的时候,清除定时器,否则定时器会继续存在,系统不会自动清除 |
||||
beforeDestroy() { |
||||
this.setTimeToStorage() |
||||
clearTimeout(this.timer) |
||||
this.timer = null |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
</style> |
||||
@ -0,0 +1,30 @@ |
||||
import defprops from '../../libs/config/props'; |
||||
export default { |
||||
props: { |
||||
// 占父容器宽度的多少等分,总分为12份
|
||||
span: { |
||||
type: [String, Number], |
||||
default: defprops.col.span |
||||
}, |
||||
// 指定栅格左侧的间隔数(总12栏)
|
||||
offset: { |
||||
type: [String, Number], |
||||
default: defprops.col.offset |
||||
}, |
||||
// 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
|
||||
justify: { |
||||
type: String, |
||||
default: defprops.col.justify |
||||
}, |
||||
// 垂直对齐方式,可选值为top、center、bottom、stretch
|
||||
align: { |
||||
type: String, |
||||
default: defprops.col.align |
||||
}, |
||||
// 文字对齐方式
|
||||
textAlign: { |
||||
type: String, |
||||
default: defprops.col.textAlign |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,168 @@ |
||||
<template> |
||||
<view |
||||
class="u-col" |
||||
ref="u-col" |
||||
:class="[ |
||||
'u-col-' + span |
||||
]" |
||||
:style="[colStyle]" |
||||
@tap="clickHandler" |
||||
> |
||||
<slot></slot> |
||||
</view> |
||||
</template> |
||||
|
||||
<script> |
||||
import props from './props.js'; |
||||
import mpMixin from '../../libs/mixin/mpMixin.js'; |
||||
import mixin from '../../libs/mixin/mixin.js'; |
||||
/** |
||||
* CodeInput 栅格系统的列 |
||||
* @description 该组件一般用于Layout 布局 通过基础的 12 分栏,迅速简便地创建布局 |
||||
* @tutorial https://ijry.github.io/uview-plus/components/Layout.html |
||||
* @property {String | Number} span 栅格占据的列数,总12等份 (默认 12 ) |
||||
* @property {String | Number} offset 分栏左边偏移,计算方式与span相同 (默认 0 ) |
||||
* @property {String} justify 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) (默认 'start' ) |
||||
* @property {String} align 垂直对齐方式,可选值为top、center、bottom、stretch (默认 'stretch' ) |
||||
* @property {String} textAlign 文字水平对齐方式 (默认 'left' ) |
||||
* @property {Object} customStyle 定义需要用到的外部样式 |
||||
* @event {Function} click col被点击,会阻止事件冒泡到row |
||||
* @example <u-col span="3" offset="3" > <view class="demo-layout bg-purple"></view> </u-col> |
||||
*/ |
||||
export default { |
||||
name: 'u-col', |
||||
mixins: [mpMixin, mixin, props], |
||||
data() { |
||||
return { |
||||
width: 0, |
||||
parentData: { |
||||
gutter: 0 |
||||
}, |
||||
gridNum: 12 |
||||
} |
||||
}, |
||||
// 微信小程序中 options 选项 |
||||
options: { |
||||
virtualHost: true // 将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等 |
||||
}, |
||||
computed: { |
||||
uJustify() { |
||||
if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify |
||||
else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify |
||||
else return this.justify |
||||
}, |
||||
uAlignItem() { |
||||
if (this.align == 'top') return 'flex-start' |
||||
if (this.align == 'bottom') return 'flex-end' |
||||
else return this.align |
||||
}, |
||||
colStyle() { |
||||
const style = { |
||||
// 这里写成"padding: 0 10px"的形式是因为nvue的需要 |
||||
paddingLeft: uni.$u.addUnit(uni.$u.getPx(this.parentData.gutter)/2), |
||||
paddingRight: uni.$u.addUnit(uni.$u.getPx(this.parentData.gutter)/2), |
||||
alignItems: this.uAlignItem, |
||||
justifyContent: this.uJustify, |
||||
textAlign: this.textAlign, |
||||
// #ifndef APP-NVUE |
||||
// 在非nvue上,使用百分比形式 |
||||
flex: `0 0 ${100 / this.gridNum * this.span}%`, |
||||
marginLeft: 100 / 12 * this.offset + '%', |
||||
// #endif |
||||
// #ifdef APP-NVUE |
||||
// 在nvue上,由于无法使用百分比单位,这里需要获取父组件的宽度,再计算得出该有对应的百分比尺寸 |
||||
width: uni.$u.addUnit(Math.floor(this.width / this.gridNum * Number(this.span))), |
||||
marginLeft: uni.$u.addUnit(Math.floor(this.width / this.gridNum * Number(this.offset))), |
||||
// #endif |
||||
} |
||||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) |
||||
} |
||||
}, |
||||
mounted() { |
||||
this.init() |
||||
}, |
||||
methods: { |
||||
async init() { |
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用 |
||||
this.updateParentData() |
||||
this.width = await this.parent.getComponentWidth() |
||||
}, |
||||
updateParentData() { |
||||
this.getParentData('u-row') |
||||
}, |
||||
clickHandler(e) { |
||||
this.$emit('click'); |
||||
} |
||||
}, |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" scoped> |
||||
@import "../../libs/css/components.scss"; |
||||
|
||||
.u-col { |
||||
padding: 0; |
||||
/* #ifndef APP-NVUE */ |
||||
box-sizing:border-box; |
||||
/* #endif */ |
||||
/* #ifdef MP */ |
||||
display: block; |
||||
/* #endif */ |
||||
} |
||||
|
||||
// nvue下百分比无效 |
||||
/* #ifndef APP-NVUE */ |
||||
.u-col-0 { |
||||
width: 0; |
||||
} |
||||
|
||||
.u-col-1 { |
||||
width: calc(100%/12); |
||||
} |
||||
|
||||
.u-col-2 { |
||||
width: calc(100%/12 * 2); |
||||
} |
||||
|
||||
.u-col-3 { |
||||
width: calc(100%/12 * 3); |
||||
} |
||||
|
||||
.u-col-4 { |
||||
width: calc(100%/12 * 4); |
||||
} |
||||
|
||||
.u-col-5 { |
||||
width: calc(100%/12 * 5); |
||||
} |
||||
|
||||
.u-col-6 { |
||||
width: calc(100%/12 * 6); |
||||
} |
||||
|
||||
.u-col-7 { |
||||
width: calc(100%/12 * 7); |
||||
} |
||||
|
||||
.u-col-8 { |
||||
width: calc(100%/12 * 8); |
||||
} |
||||
|
||||
.u-col-9 { |
||||
width: calc(100%/12 * 9); |
||||
} |
||||
|
||||
.u-col-10 { |
||||
width: calc(100%/12 * 10); |
||||
} |
||||
|
||||
.u-col-11 { |
||||
width: calc(100%/12 * 11); |
||||
} |
||||
|
||||
.u-col-12 { |
||||
width: calc(100%/12 * 12); |
||||
} |
||||
|
||||
/* #endif */ |
||||
</style> |
||||