add
commit
d976ca98ca
|
@ -0,0 +1,16 @@
|
|||
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||
"version": "0.0",
|
||||
"configurations": [{
|
||||
"default" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"mp-weixin" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<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 "uview-ui/index.scss";
|
||||
</style>
|
|
@ -0,0 +1,28 @@
|
|||
// 可修改的公共变量
|
||||
let type = 'app'
|
||||
// #ifdef APP-PLUS
|
||||
type = "app"
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
type = "h5"
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
type = "wxMini"
|
||||
// #endif
|
||||
|
||||
// 在maps里面有一个地图的key,需要手动改变
|
||||
const variable = {
|
||||
|
||||
name: "", //应用名字
|
||||
whatType: type, //判断当前是哪端
|
||||
// baseUrl: "http://hy.ecbeauty.cn", //线上测试接口的基础路径
|
||||
baseUrl: "https://api.lihe-control.com", //线上接口的基础路径
|
||||
|
||||
|
||||
mapKey: "", //jsapi
|
||||
webKey: '', //webApi
|
||||
wxMapKey: "", //小程序key
|
||||
socket: ''
|
||||
|
||||
}
|
||||
export default variable
|
|
@ -0,0 +1,27 @@
|
|||
import variable from '@/api/commonVariable.js'
|
||||
|
||||
// 司机端接口
|
||||
let api = {
|
||||
|
||||
// 公共key
|
||||
baseUrl: variable.baseUrl,
|
||||
|
||||
|
||||
user_login:'/user/login',// 登录
|
||||
selFs_time:'/selFs/time',//主页查看施肥机历史数据
|
||||
user_logout:'/user/logout',//退出
|
||||
user_upload:'/user/upload',//上传logo
|
||||
update_user:"/api/update/user",//用户更新个人信息,修改昵称密码等
|
||||
sel_user:"/api/sel/user",//根据id查询用户信息
|
||||
getFs_rtdata:"/getFs/rtdata",//主页查看施肥机实时数据
|
||||
selFs_time:"/selFs/time",//主页查看施肥机历史数据
|
||||
sel_eqbyid:"/sel/eqbyid/",//根据用户id查询对应的设备数据
|
||||
chart_fsdata:"/chart/fsdata",//实时数据折线图
|
||||
user_update_pwd:"/user/update/pwd",//用户修改密码
|
||||
|
||||
readFs_state:"/readFs/state",//根据设备ID获取实时数据
|
||||
writeFs:"/writeFs",//根据设备ID获取实时数据
|
||||
readFs_reg:"/readFs/reg",//根据设备ID获取实时数据
|
||||
|
||||
}
|
||||
export default api
|
|
@ -0,0 +1,53 @@
|
|||
import api from './driverapi.js'
|
||||
import variable from '@/api/commonVariable.js'
|
||||
|
||||
export const myRequest = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: api.baseUrl + options.url,
|
||||
method: options.method || 'POST',
|
||||
header: {
|
||||
token: uni.getStorageSync('token'),
|
||||
// terminal: variable.whatType,
|
||||
// 'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: options.data || {},
|
||||
success: (res) => {
|
||||
resolve(res.data)
|
||||
|
||||
if (res.data.code == 401) {
|
||||
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
uni.removeStorage({
|
||||
key: 'token',
|
||||
success() {
|
||||
uni.removeStorage({
|
||||
key: 'user_info',
|
||||
success() {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
// 接口成功可以在此写成功需要的步骤
|
||||
if (res.data.code != 1) {
|
||||
// uni.showToast({
|
||||
// title: res.data.msg,
|
||||
// icon: 'none'
|
||||
// });
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.showToast({
|
||||
title: '请求接口失败',
|
||||
icon: 'none'
|
||||
})
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Eslint config file
|
||||
* Documentation: https://eslint.org/docs/user-guide/configuring/
|
||||
* Install the Eslint extension before using this feature.
|
||||
*/
|
||||
module.exports = {
|
||||
env: {
|
||||
es6: true,
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
ecmaFeatures: {
|
||||
modules: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module',
|
||||
},
|
||||
globals: {
|
||||
wx: true,
|
||||
App: true,
|
||||
Page: true,
|
||||
getCurrentPages: true,
|
||||
getApp: true,
|
||||
Component: true,
|
||||
requirePlugin: true,
|
||||
requireMiniProgram: true,
|
||||
},
|
||||
// extends: 'eslint:recommended',
|
||||
rules: {},
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/logs/logs"
|
||||
],
|
||||
"window": {
|
||||
"backgroundTextStyle": "light",
|
||||
"navigationBarBackgroundColor": "#fff",
|
||||
"navigationBarTitleText": "Weixin",
|
||||
"navigationBarTextStyle": "black"
|
||||
},
|
||||
"style": "v2",
|
||||
"sitemapLocation": "sitemap.json"
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// app.ts
|
||||
App<IAppOption>({
|
||||
globalData: {},
|
||||
onLaunch() {
|
||||
// 展示本地存储能力
|
||||
const logs = wx.getStorageSync('logs') || []
|
||||
logs.unshift(Date.now())
|
||||
wx.setStorageSync('logs', logs)
|
||||
|
||||
// 登录
|
||||
wx.login({
|
||||
success: res => {
|
||||
console.log(res.code)
|
||||
// 发送 res.code 到后台换取 openId, sessionKey, unionId
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
|
@ -0,0 +1,10 @@
|
|||
/**app.wxss**/
|
||||
.container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 200rpx 0;
|
||||
box-sizing: border-box;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"usingComponents": {}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// index.ts
|
||||
// 获取应用实例
|
||||
const app = getApp<IAppOption>()
|
||||
|
||||
Page({
|
||||
data: {
|
||||
motto: 'Hello World',
|
||||
userInfo: {},
|
||||
hasUserInfo: false,
|
||||
canIUse: wx.canIUse('button.open-type.getUserInfo'),
|
||||
canIUseGetUserProfile: false,
|
||||
canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') && wx.canIUse('open-data.type.userNickName') // 如需尝试获取用户信息可改为false
|
||||
},
|
||||
// 事件处理函数
|
||||
bindViewTap() {
|
||||
wx.navigateTo({
|
||||
url: '../logs/logs',
|
||||
})
|
||||
},
|
||||
onLoad() {
|
||||
// @ts-ignore
|
||||
if (wx.getUserProfile) {
|
||||
this.setData({
|
||||
canIUseGetUserProfile: true
|
||||
})
|
||||
}
|
||||
},
|
||||
getUserProfile() {
|
||||
// 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
|
||||
wx.getUserProfile({
|
||||
desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
|
||||
success: (res) => {
|
||||
console.log(res)
|
||||
this.setData({
|
||||
userInfo: res.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getUserInfo(e: any) {
|
||||
// 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
|
||||
console.log(e)
|
||||
this.setData({
|
||||
userInfo: e.detail.userInfo,
|
||||
hasUserInfo: true
|
||||
})
|
||||
}
|
||||
})
|
|
@ -0,0 +1,23 @@
|
|||
<!--index.wxml-->
|
||||
<view class="container">
|
||||
<view class="userinfo">
|
||||
<block wx:if="{{canIUseOpenData}}">
|
||||
<view class="userinfo-avatar" bindtap="bindViewTap">
|
||||
<open-data type="userAvatarUrl"></open-data>
|
||||
</view>
|
||||
<open-data type="userNickName"></open-data>
|
||||
</block>
|
||||
<block wx:elif="{{!hasUserInfo}}">
|
||||
<button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
|
||||
<button wx:elif="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
|
||||
<view wx:else> 请使用1.4.4及以上版本基础库 </view>
|
||||
</block>
|
||||
<block wx:else>
|
||||
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
|
||||
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
|
||||
</block>
|
||||
</view>
|
||||
<view class="usermotto">
|
||||
<text class="user-motto">{{motto}}</text>
|
||||
</view>
|
||||
</view>
|
|
@ -0,0 +1,19 @@
|
|||
/**index.wxss**/
|
||||
.userinfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.userinfo-avatar {
|
||||
overflow: hidden;
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
margin: 20rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.usermotto {
|
||||
margin-top: 200px;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"navigationBarTitleText": "查看启动日志",
|
||||
"usingComponents": {}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// logs.ts
|
||||
// const util = require('../../utils/util.js')
|
||||
import { formatTime } from '../../utils/util'
|
||||
|
||||
Page({
|
||||
data: {
|
||||
logs: [],
|
||||
},
|
||||
onLoad() {
|
||||
this.setData({
|
||||
logs: (wx.getStorageSync('logs') || []).map((log: string) => {
|
||||
return {
|
||||
date: formatTime(new Date(log)),
|
||||
timeStamp: log
|
||||
}
|
||||
}),
|
||||
})
|
||||
},
|
||||
})
|
|
@ -0,0 +1,6 @@
|
|||
<!--logs.wxml-->
|
||||
<view class="container log-list">
|
||||
<block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
|
||||
<text class="log-item">{{index + 1}}. {{log.date}}</text>
|
||||
</block>
|
||||
</view>
|
|
@ -0,0 +1,8 @@
|
|||
.log-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 40rpx;
|
||||
}
|
||||
.log-item {
|
||||
margin: 10rpx;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
|
||||
"rules": [{
|
||||
"action": "allow",
|
||||
"page": "*"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
export const formatTime = (date: Date) => {
|
||||
const year = date.getFullYear()
|
||||
const month = date.getMonth() + 1
|
||||
const day = date.getDate()
|
||||
const hour = date.getHours()
|
||||
const minute = date.getMinutes()
|
||||
const second = date.getSeconds()
|
||||
|
||||
return (
|
||||
[year, month, day].map(formatNumber).join('/') +
|
||||
' ' +
|
||||
[hour, minute, second].map(formatNumber).join(':')
|
||||
)
|
||||
}
|
||||
|
||||
const formatNumber = (n: number) => {
|
||||
const s = n.toString()
|
||||
return s[1] ? s : '0' + s
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "miniprogram-ts-less-quickstart",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"miniprogram-api-typings": "^2.8.3-1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"description": "项目配置文件",
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"miniprogramRoot": "miniprogram/",
|
||||
"compileType": "miniprogram",
|
||||
"projectname": "ts-demo",
|
||||
"setting": {
|
||||
"useCompilerPlugins": [
|
||||
"typescript"
|
||||
],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
}
|
||||
},
|
||||
"simulatorType": "wechat",
|
||||
"simulatorPluginLibVersion": {},
|
||||
"condition": {},
|
||||
"srcMiniprogramRoot": "miniprogram/",
|
||||
"appid": "wx05b45a2699f02a2b",
|
||||
"libVersion": "3.1.1",
|
||||
"editorSetting": {
|
||||
"tabIndent": "insertSpaces",
|
||||
"tabSize": 2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
|
||||
"projectname": "dev",
|
||||
"setting": {
|
||||
"compileHotReLoad": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"module": "CommonJS",
|
||||
"target": "ES2020",
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitReturns": true,
|
||||
"alwaysStrict": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strict": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"lib": ["ES2020"],
|
||||
"typeRoots": [
|
||||
"./typings"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference path="./types/index.d.ts" />
|
||||
|
||||
interface IAppOption {
|
||||
globalData: {
|
||||
userInfo?: WechatMiniprogram.UserInfo,
|
||||
}
|
||||
userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback,
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/// <reference path="./wx/index.d.ts" />
|
|
@ -0,0 +1,165 @@
|
|||
/*! *****************************************************************************
|
||||
Copyright (c) 2023 Tencent, Inc. All rights reserved.
|
||||
|
||||
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.
|
||||
***************************************************************************** */
|
||||
|
||||
/// <reference path="./lib.wx.app.d.ts" />
|
||||
/// <reference path="./lib.wx.page.d.ts" />
|
||||
/// <reference path="./lib.wx.api.d.ts" />
|
||||
/// <reference path="./lib.wx.cloud.d.ts" />
|
||||
/// <reference path="./lib.wx.canvas.d.ts" />
|
||||
/// <reference path="./lib.wx.component.d.ts" />
|
||||
/// <reference path="./lib.wx.behavior.d.ts" />
|
||||
/// <reference path="./lib.wx.event.d.ts" />
|
||||
/// <reference path="./lib.wx.wasm.d.ts" />
|
||||
|
||||
declare namespace WechatMiniprogram {
|
||||
type IAnyObject = Record<string, any>
|
||||
type Optional<F> = F extends (arg: infer P) => infer R ? (arg?: P) => R : F
|
||||
type OptionalInterface<T> = { [K in keyof T]: Optional<T[K]> }
|
||||
interface AsyncMethodOptionLike {
|
||||
success?: (...args: any[]) => void
|
||||
}
|
||||
type PromisifySuccessResult<
|
||||
P,
|
||||
T extends AsyncMethodOptionLike
|
||||
> = P extends {
|
||||
success: any
|
||||
}
|
||||
? void
|
||||
: P extends { fail: any }
|
||||
? void
|
||||
: P extends { complete: any }
|
||||
? void
|
||||
: Promise<Parameters<Exclude<T['success'], undefined>>[0]>
|
||||
|
||||
// TODO: Extract real definition from `lib.dom.d.ts` to replace this
|
||||
type IIRFilterNode = any
|
||||
type WaveShaperNode = any
|
||||
type ConstantSourceNode = any
|
||||
type OscillatorNode = any
|
||||
type GainNode = any
|
||||
type BiquadFilterNode = any
|
||||
type PeriodicWaveNode = any
|
||||
type AudioNode = any
|
||||
type AudioParam = any
|
||||
type ChannelSplitterNode = any
|
||||
type ChannelMergerNode = any
|
||||
type DelayNode = any
|
||||
type DynamicsCompressorNode = any
|
||||
type ScriptProcessorNode = any
|
||||
type PannerNode = any
|
||||
type AnalyserNode = any
|
||||
type AudioListener = any
|
||||
type WebGLTexture = any
|
||||
type WebGLRenderingContext = any
|
||||
|
||||
// TODO: fill worklet type
|
||||
type WorkletFunction = (...args: any) => any
|
||||
type AnimationObject = any
|
||||
type SharedValue<T = any> = T
|
||||
type DerivedValue<T = any> = T
|
||||
}
|
||||
|
||||
declare let console: WechatMiniprogram.Console
|
||||
|
||||
declare let wx: WechatMiniprogram.Wx
|
||||
/** 引入模块。返回模块通过 `module.exports` 或 `exports` 暴露的接口。 */
|
||||
interface Require {
|
||||
(
|
||||
/** 需要引入模块文件相对于当前文件的相对路径,或 npm 模块名,或 npm 模块路径。不支持绝对路径 */
|
||||
module: string,
|
||||
/** 用于异步获取其他分包中的模块的引用结果,详见 [分包异步化]((subpackages/async)) */
|
||||
callback?: (moduleExport: any) => void,
|
||||
/** 异步获取分包失败时的回调,详见 [分包异步化]((subpackages/async)) */
|
||||
errorCallback?: (err: any) => void
|
||||
): any
|
||||
/** 以 Promise 形式异步引入模块。返回模块通过 `module.exports` 或 `exports` 暴露的接口。 */
|
||||
async(
|
||||
/** 需要引入模块文件相对于当前文件的相对路径,或 npm 模块名,或 npm 模块路径。不支持绝对路径 */
|
||||
module: string
|
||||
): Promise<any>
|
||||
}
|
||||
declare const require: Require
|
||||
/** 引入插件。返回插件通过 `main` 暴露的接口。 */
|
||||
interface RequirePlugin {
|
||||
(
|
||||
/** 需要引入的插件的 alias */
|
||||
module: string,
|
||||
/** 用于异步获取其他分包中的插件的引用结果,详见 [分包异步化]((subpackages/async)) */
|
||||
callback?: (pluginExport: any) => void
|
||||
): any
|
||||
/** 以 Promise 形式异步引入插件。返回插件通过 `main` 暴露的接口。 */
|
||||
async(
|
||||
/** 需要引入的插件的 alias */
|
||||
module: string
|
||||
): Promise<any>
|
||||
}
|
||||
declare const requirePlugin: RequirePlugin
|
||||
/** 插件引入当前使用者小程序。返回使用者小程序通过 [插件配置中 `export` 暴露的接口](https://developers.weixin.qq.com/miniprogram/dev/framework/plugin/using.html#%E5%AF%BC%E5%87%BA%E5%88%B0%E6%8F%92%E4%BB%B6)。
|
||||
*
|
||||
* 该接口只在插件中存在
|
||||
*
|
||||
* 最低基础库: `2.11.1` */
|
||||
declare function requireMiniProgram(): any
|
||||
/** 当前模块对象 */
|
||||
declare let module: {
|
||||
/** 模块向外暴露的对象,使用 `require` 引用该模块时可以获取 */
|
||||
exports: any
|
||||
}
|
||||
/** `module.exports` 的引用 */
|
||||
declare let exports: any
|
||||
|
||||
/** [clearInterval(number intervalID)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/clearInterval.html)
|
||||
*
|
||||
* 取消由 setInterval 设置的定时器。 */
|
||||
declare function clearInterval(
|
||||
/** 要取消的定时器的 ID */
|
||||
intervalID: number
|
||||
): void
|
||||
/** [clearTimeout(number timeoutID)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/clearTimeout.html)
|
||||
*
|
||||
* 取消由 setTimeout 设置的定时器。 */
|
||||
declare function clearTimeout(
|
||||
/** 要取消的定时器的 ID */
|
||||
timeoutID: number
|
||||
): void
|
||||
/** [number setInterval(function callback, number delay, any rest)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/setInterval.html)
|
||||
*
|
||||
* 设定一个定时器。按照指定的周期(以毫秒计)来执行注册的回调函数 */
|
||||
declare function setInterval(
|
||||
/** 回调函数 */
|
||||
callback: (...args: any[]) => any,
|
||||
/** 执行回调函数之间的时间间隔,单位 ms。 */
|
||||
delay?: number,
|
||||
/** param1, param2, ..., paramN 等附加参数,它们会作为参数传递给回调函数。 */
|
||||
rest?: any
|
||||
): number
|
||||
/** [number setTimeout(function callback, number delay, any rest)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/setTimeout.html)
|
||||
*
|
||||
* 设定一个定时器。在定时到期以后执行注册的回调函数 */
|
||||
declare function setTimeout(
|
||||
/** 回调函数 */
|
||||
callback: (...args: any[]) => any,
|
||||
/** 延迟的时间,函数的调用会在该延迟之后发生,单位 ms。 */
|
||||
delay?: number,
|
||||
/** param1, param2, ..., paramN 等附加参数,它们会作为参数传递给回调函数。 */
|
||||
rest?: any
|
||||
): number
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,400 @@
|
|||
/*! *****************************************************************************
|
||||
Copyright (c) 2023 Tencent, Inc. All rights reserved.
|
||||
|
||||
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.
|
||||
***************************************************************************** */
|
||||
|
||||
declare namespace WechatMiniprogram.App {
|
||||
type SceneValues =
|
||||
| 1000
|
||||
| 1001
|
||||
| 1005
|
||||
| 1006
|
||||
| 1007
|
||||
| 1008
|
||||
| 1010
|
||||
| 1011
|
||||
| 1012
|
||||
| 1013
|
||||
| 1014
|
||||
| 1017
|
||||
| 1019
|
||||
| 1020
|
||||
| 1022
|
||||
| 1023
|
||||
| 1024
|
||||
| 1025
|
||||
| 1026
|
||||
| 1027
|
||||
| 1028
|
||||
| 1029
|
||||
| 1030
|
||||
| 1031
|
||||
| 1032
|
||||
| 1034
|
||||
| 1035
|
||||
| 1036
|
||||
| 1037
|
||||
| 1038
|
||||
| 1039
|
||||
| 1042
|
||||
| 1043
|
||||
| 1044
|
||||
| 1045
|
||||
| 1046
|
||||
| 1047
|
||||
| 1048
|
||||
| 1049
|
||||
| 1052
|
||||
| 1053
|
||||
| 1054
|
||||
| 1056
|
||||
| 1057
|
||||
| 1058
|
||||
| 1059
|
||||
| 1060
|
||||
| 1064
|
||||
| 1065
|
||||
| 1067
|
||||
| 1068
|
||||
| 1069
|
||||
| 1071
|
||||
| 1072
|
||||
| 1073
|
||||
| 1074
|
||||
| 1077
|
||||
| 1078
|
||||
| 1079
|
||||
| 1081
|
||||
| 1082
|
||||
| 1084
|
||||
| 1088
|
||||
| 1089
|
||||
| 1090
|
||||
| 1091
|
||||
| 1092
|
||||
| 1095
|
||||
| 1096
|
||||
| 1097
|
||||
| 1099
|
||||
| 1100
|
||||
| 1101
|
||||
| 1102
|
||||
| 1103
|
||||
| 1104
|
||||
| 1106
|
||||
| 1107
|
||||
| 1113
|
||||
| 1114
|
||||
| 1119
|
||||
| 1120
|
||||
| 1121
|
||||
| 1124
|
||||
| 1125
|
||||
| 1126
|
||||
| 1129
|
||||
| 1131
|
||||
| 1133
|
||||
| 1135
|
||||
| 1144
|
||||
| 1145
|
||||
| 1146
|
||||
| 1148
|
||||
| 1150
|
||||
| 1151
|
||||
| 1152
|
||||
| 1153
|
||||
| 1154
|
||||
| 1155
|
||||
| 1157
|
||||
| 1158
|
||||
| 1160
|
||||
| 1167
|
||||
| 1168
|
||||
| 1169
|
||||
| 1171
|
||||
| 1173
|
||||
| 1175
|
||||
| 1176
|
||||
| 1177
|
||||
| 1178
|
||||
| 1179
|
||||
| 1181
|
||||
| 1183
|
||||
| 1184
|
||||
| 1185
|
||||
| 1186
|
||||
| 1187
|
||||
| 1189
|
||||
| 1191
|
||||
| 1192
|
||||
| 1193
|
||||
| 1194
|
||||
| 1195
|
||||
| 1196
|
||||
| 1197
|
||||
| 1198
|
||||
| 1200
|
||||
| 1201
|
||||
| 1202
|
||||
| 1203
|
||||
| 1206
|
||||
| 1207
|
||||
| 1208
|
||||
| 1212
|
||||
| 1215
|
||||
| 1216
|
||||
| 1223
|
||||
| 1228
|
||||
| 1231
|
||||
|
||||
interface LaunchShowOption {
|
||||
/** 打开小程序的路径 */
|
||||
path: string
|
||||
/** 打开小程序的query */
|
||||
query: IAnyObject
|
||||
/** 打开小程序的场景值
|
||||
* - 1000:其他
|
||||
* - 1001:发现栏小程序主入口,「最近使用」列表(基础库 2.2.4 版本起包含「我的小程序」列表)
|
||||
* - 1005:微信首页顶部搜索框的搜索结果页
|
||||
* - 1006:发现栏小程序主入口搜索框的搜索结果页
|
||||
* - 1007:单人聊天会话中的小程序消息卡片
|
||||
* - 1008:群聊会话中的小程序消息卡片
|
||||
* - 1010:收藏夹
|
||||
* - 1011:扫描二维码
|
||||
* - 1012:长按图片识别二维码
|
||||
* - 1013:扫描手机相册中选取的二维码
|
||||
* - 1014:小程序订阅消息(与 1107 相同)
|
||||
* - 1017:前往小程序体验版的入口页
|
||||
* - 1019:微信钱包(微信客户端 7.0.0 版本改为支付入口)
|
||||
* - 1020:公众号 profile 页相关小程序列表(已废弃)
|
||||
* - 1022:聊天顶部置顶小程序入口(微信客户端 6.6.1 版本起废弃)
|
||||
* - 1023:安卓系统桌面图标
|
||||
* - 1024:小程序 profile 页
|
||||
* - 1025:扫描一维码
|
||||
* - 1026:发现栏小程序主入口,「附近的小程序」列表
|
||||
* - 1027:微信首页顶部搜索框搜索结果页「使用过的小程序」列表
|
||||
* - 1028:我的卡包
|
||||
* - 1029:小程序中的卡券详情页
|
||||
* - 1030:自动化测试下打开小程序
|
||||
* - 1031:长按图片识别一维码
|
||||
* - 1032:扫描手机相册中选取的一维码
|
||||
* - 1034:微信支付完成页
|
||||
* - 1035:公众号自定义菜单
|
||||
* - 1036:App 分享消息卡片
|
||||
* - 1037:小程序打开小程序
|
||||
* - 1038:从另一个小程序返回
|
||||
* - 1039:摇电视
|
||||
* - 1042:添加好友搜索框的搜索结果页
|
||||
* - 1043:公众号模板消息
|
||||
* - 1044:带 shareTicket 的小程序消息卡片 [详情](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share.html)
|
||||
* - 1045:朋友圈广告
|
||||
* - 1046:朋友圈广告详情页
|
||||
* - 1047:扫描小程序码
|
||||
* - 1048:长按图片识别小程序码
|
||||
* - 1049:扫描手机相册中选取的小程序码
|
||||
* - 1052:卡券的适用门店列表
|
||||
* - 1053:搜一搜的结果页
|
||||
* - 1054:顶部搜索框小程序快捷入口(微信客户端版本 6.7.4 起废弃)
|
||||
* - 1056:聊天顶部音乐播放器右上角菜单
|
||||
* - 1057:钱包中的银行卡详情页
|
||||
* - 1058:公众号文章
|
||||
* - 1059:体验版小程序绑定邀请页
|
||||
* - 1060:微信支付完成页(与 1034 相同)
|
||||
* - 1064:微信首页连 Wi-Fi 状态栏
|
||||
* - 1065:URL scheme [详情](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-scheme.html)
|
||||
* - 1067:公众号文章广告
|
||||
* - 1068:附近小程序列表广告(已废弃)
|
||||
* - 1069:移动应用通过 openSDK 进入微信,打开小程序
|
||||
* - 1071:钱包中的银行卡列表页
|
||||
* - 1072:二维码收款页面
|
||||
* - 1073:客服消息列表下发的小程序消息卡片
|
||||
* - 1074:公众号会话下发的小程序消息卡片
|
||||
* - 1077:摇周边
|
||||
* - 1078:微信连 Wi-Fi 成功提示页
|
||||
* - 1079:微信游戏中心
|
||||
* - 1081:客服消息下发的文字链
|
||||
* - 1082:公众号会话下发的文字链
|
||||
* - 1084:朋友圈广告原生页
|
||||
* - 1088:会话中查看系统消息,打开小程序
|
||||
* - 1089:微信聊天主界面下拉,「最近使用」栏(基础库 2.2.4 版本起包含「我的小程序」栏)
|
||||
* - 1090:长按小程序右上角菜单唤出最近使用历史
|
||||
* - 1091:公众号文章商品卡片
|
||||
* - 1092:城市服务入口
|
||||
* - 1095:小程序广告组件
|
||||
* - 1096:聊天记录,打开小程序
|
||||
* - 1097:微信支付签约原生页,打开小程序
|
||||
* - 1099:页面内嵌插件
|
||||
* - 1100:红包封面详情页打开小程序
|
||||
* - 1101:远程调试热更新(开发者工具中,预览 -> 自动预览 -> 编译并预览)
|
||||
* - 1102:公众号 profile 页服务预览
|
||||
* - 1103:发现栏小程序主入口,「我的小程序」列表(基础库 2.2.4 版本起废弃)
|
||||
* - 1104:微信聊天主界面下拉,「我的小程序」栏(基础库 2.2.4 版本起废弃)
|
||||
* - 1106:聊天主界面下拉,从顶部搜索结果页,打开小程序
|
||||
* - 1107:订阅消息,打开小程序
|
||||
* - 1113:安卓手机负一屏,打开小程序(三星)
|
||||
* - 1114:安卓手机侧边栏,打开小程序(三星)
|
||||
* - 1119:【企业微信】工作台内打开小程序
|
||||
* - 1120:【企业微信】个人资料页内打开小程序
|
||||
* - 1121:【企业微信】聊天加号附件框内打开小程序
|
||||
* - 1124:扫“一物一码”打开小程序
|
||||
* - 1125:长按图片识别“一物一码”
|
||||
* - 1126:扫描手机相册中选取的“一物一码”
|
||||
* - 1129:微信爬虫访问 [详情](https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/sitemap.html)
|
||||
* - 1131:浮窗(8.0 版本起仅包含被动浮窗)
|
||||
* - 1133:硬件设备打开小程序 [详情](https://developers.weixin.qq.com/doc/oplatform/Miniprogram_Frame/)
|
||||
* - 1135:小程序 profile 页相关小程序列表,打开小程序
|
||||
* - 1144:公众号文章 - 视频贴片
|
||||
* - 1145:发现栏 - 发现小程序
|
||||
* - 1146:地理位置信息打开出行类小程序
|
||||
* - 1148:卡包-交通卡,打开小程序
|
||||
* - 1150:扫一扫商品条码结果页打开小程序
|
||||
* - 1151:发现栏 - 我的订单
|
||||
* - 1152:订阅号视频打开小程序
|
||||
* - 1153:“识物”结果页打开小程序
|
||||
* - 1154:朋友圈内打开“单页模式”
|
||||
* - 1155:“单页模式”打开小程序
|
||||
* - 1157:服务号会话页打开小程序
|
||||
* - 1158:群工具打开小程序
|
||||
* - 1160:群待办
|
||||
* - 1167:H5 通过开放标签打开小程序 [详情](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html)
|
||||
* - 1168:移动应用直接运行小程序
|
||||
* - 1169:发现栏小程序主入口,各个生活服务入口(例如快递服务、出行服务等)
|
||||
* - 1171:微信运动记录(仅安卓)
|
||||
* - 1173:聊天素材用小程序打开 [详情](https://developers.weixin.qq.com/miniprogram/dev/framework/material/support_material.html)
|
||||
* - 1175:视频号主页商店入口
|
||||
* - 1176:视频号直播间主播打开小程序
|
||||
* - 1177:视频号直播商品
|
||||
* - 1178:在电脑打开手机上打开的小程序
|
||||
* - 1179:#话题页打开小程序
|
||||
* - 1181:网站应用打开 PC 小程序
|
||||
* - 1183:PC 微信 - 小程序面板 - 发现小程序 - 搜索
|
||||
* - 1184:视频号链接打开小程序
|
||||
* - 1185:群公告
|
||||
* - 1186:收藏 - 笔记
|
||||
* - 1187:浮窗(8.0 版本起)
|
||||
* - 1189:表情雨广告
|
||||
* - 1191:视频号活动
|
||||
* - 1192:企业微信联系人 profile 页
|
||||
* - 1193:视频号主页服务菜单打开小程序
|
||||
* - 1194:URL Link [详情](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-link.html)
|
||||
* - 1195:视频号主页商品 tab
|
||||
* - 1196:个人状态打开小程序
|
||||
* - 1197:视频号主播从直播间返回小游戏
|
||||
* - 1198:视频号开播界面打开小游戏
|
||||
* - 1200:视频号广告打开小程序
|
||||
* - 1201:视频号广告详情页打开小程序
|
||||
* - 1202:企微客服号会话打开小程序卡片
|
||||
* - 1203:微信小程序压测工具的请求
|
||||
* - 1206:视频号小游戏直播间打开小游戏
|
||||
* - 1207:企微客服号会话打开小程序文字链
|
||||
* - 1208:聊天打开商品卡片
|
||||
* - 1212:青少年模式申请页打开小程序
|
||||
* - 1215:广告预约打开小程序
|
||||
* - 1216:视频号订单中心打开小程序
|
||||
* - 1223:安卓桌面 Widget 打开小程序
|
||||
* - 1228:视频号原生广告组件打开小程序
|
||||
* - 1231:动态消息提醒入口打开小程序
|
||||
*/
|
||||
scene: SceneValues
|
||||
/** shareTicket,详见 [获取更多转发信息]((转发#获取更多转发信息)) */
|
||||
shareTicket: string
|
||||
/** 当场景为由从另一个小程序或公众号或App打开时,返回此字段 */
|
||||
referrerInfo?: ReferrerInfo
|
||||
}
|
||||
|
||||
interface PageNotFoundOption {
|
||||
/** 不存在页面的路径 */
|
||||
path: string
|
||||
/** 打开不存在页面的 query */
|
||||
query: IAnyObject
|
||||
/** 是否本次启动的首个页面(例如从分享等入口进来,首个页面是开发者配置的分享页面) */
|
||||
isEntryPage: boolean
|
||||
}
|
||||
|
||||
interface Option {
|
||||
/** 生命周期回调—监听小程序初始化
|
||||
*
|
||||
* 小程序初始化完成时触发,全局只触发一次。
|
||||
*/
|
||||
onLaunch(options: LaunchShowOption): void
|
||||
/** 生命周期回调—监听小程序显示
|
||||
*
|
||||
* 小程序启动,或从后台进入前台显示时
|
||||
*/
|
||||
onShow(options: LaunchShowOption): void
|
||||
/** 生命周期回调—监听小程序隐藏
|
||||
*
|
||||
* 小程序从前台进入后台时
|
||||
*/
|
||||
onHide(): void
|
||||
/** 错误监听函数
|
||||
*
|
||||
* 小程序发生脚本错误,或者 api
|
||||
*/
|
||||
onError(/** 错误信息,包含堆栈 */ error: string): void
|
||||
/** 页面不存在监听函数
|
||||
*
|
||||
* 小程序要打开的页面不存在时触发,会带上页面信息回调该函数
|
||||
*
|
||||
* **注意:**
|
||||
* 1. 如果开发者没有添加 `onPageNotFound` 监听,当跳转页面不存在时,将推入微信客户端原生的页面不存在提示页面。
|
||||
* 2. 如果 `onPageNotFound` 回调中又重定向到另一个不存在的页面,将推入微信客户端原生的页面不存在提示页面,并且不再回调 `onPageNotFound`。
|
||||
*
|
||||
* 最低基础库: 1.9.90
|
||||
*/
|
||||
onPageNotFound(options: PageNotFoundOption): void
|
||||
/**
|
||||
* 小程序有未处理的 Promise 拒绝时触发。也可以使用 [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html) 绑定监听。注意事项请参考 [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html)。
|
||||
* **参数**:与 [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html) 一致
|
||||
*/
|
||||
onUnhandledRejection: OnUnhandledRejectionCallback
|
||||
/**
|
||||
* 系统切换主题时触发。也可以使用 wx.onThemeChange 绑定监听。
|
||||
*
|
||||
* 最低基础库: 2.11.0
|
||||
*/
|
||||
onThemeChange: OnThemeChangeCallback
|
||||
}
|
||||
|
||||
type Instance<T extends IAnyObject> = Option & T
|
||||
type Options<T extends IAnyObject> = Partial<Option> &
|
||||
T &
|
||||
ThisType<Instance<T>>
|
||||
type TrivialInstance = Instance<IAnyObject>
|
||||
|
||||
interface Constructor {
|
||||
<T extends IAnyObject>(options: Options<T>): void
|
||||
}
|
||||
|
||||
interface GetAppOption {
|
||||
/** 在 `App` 未定义时返回默认实现。当App被调用时,默认实现中定义的属性会被覆盖合并到App中。一般用于独立分包
|
||||
*
|
||||
* 最低基础库: 2.2.4
|
||||
*/
|
||||
allowDefault?: boolean
|
||||
}
|
||||
|
||||
interface GetApp {
|
||||
<T extends IAnyObject = IAnyObject>(opts?: GetAppOption): Instance<T>
|
||||
}
|
||||
}
|
||||
|
||||
declare let App: WechatMiniprogram.App.Constructor
|
||||
declare let getApp: WechatMiniprogram.App.GetApp
|
|
@ -0,0 +1,68 @@
|
|||
/*! *****************************************************************************
|
||||
Copyright (c) 2023 Tencent, Inc. All rights reserved.
|
||||
|
||||
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.
|
||||
***************************************************************************** */
|
||||
|
||||
declare namespace WechatMiniprogram.Behavior {
|
||||
type BehaviorIdentifier = string
|
||||
type Instance<
|
||||
TData extends DataOption,
|
||||
TProperty extends PropertyOption,
|
||||
TMethod extends MethodOption,
|
||||
TCustomInstanceProperty extends IAnyObject = Record<string, never>
|
||||
> = Component.Instance<TData, TProperty, TMethod, TCustomInstanceProperty>
|
||||
type TrivialInstance = Instance<IAnyObject, IAnyObject, IAnyObject>
|
||||
type TrivialOption = Options<IAnyObject, IAnyObject, IAnyObject>
|
||||
type Options<
|
||||
TData extends DataOption,
|
||||
TProperty extends PropertyOption,
|
||||
TMethod extends MethodOption,
|
||||
TCustomInstanceProperty extends IAnyObject = Record<string, never>
|
||||
> = Partial<Data<TData>> &
|
||||
Partial<Property<TProperty>> &
|
||||
Partial<Method<TMethod>> &
|
||||
Partial<OtherOption> &
|
||||
Partial<Lifetimes> &
|
||||
ThisType<Instance<TData, TProperty, TMethod, TCustomInstanceProperty>>
|
||||
interface Constructor {
|
||||
<
|
||||
TData extends DataOption,
|
||||
TProperty extends PropertyOption,
|
||||
TMethod extends MethodOption,
|
||||
TCustomInstanceProperty extends IAnyObject = Record<string, never>
|
||||
>(
|
||||
options: Options<TData, TProperty, TMethod, TCustomInstanceProperty>
|
||||
): BehaviorIdentifier
|
||||
}
|
||||
|
||||
type DataOption = Component.DataOption
|
||||
type PropertyOption = Component.PropertyOption
|
||||
type MethodOption = Component.MethodOption
|
||||
type Data<D extends DataOption> = Component.Data<D>
|
||||
type Property<P extends PropertyOption> = Component.Property<P>
|
||||
type Method<M extends MethodOption> = Component.Method<M>
|
||||
|
||||
type DefinitionFilter = Component.DefinitionFilter
|
||||
type Lifetimes = Component.Lifetimes
|
||||
|
||||
type OtherOption = Omit<Component.OtherOption, 'options'>
|
||||
}
|
||||
/** 注册一个 `behavior`,接受一个 `Object` 类型的参数。*/
|
||||
declare let Behavior: WechatMiniprogram.Behavior.Constructor
|
|
@ -0,0 +1,983 @@
|
|||
/*! *****************************************************************************
|
||||
Copyright (c) 2023 Tencent, Inc. All rights reserved.
|
||||
|
||||
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.
|
||||
***************************************************************************** */
|
||||
|
||||
/**
|
||||
* Common interfaces and types
|
||||
*/
|
||||
|
||||
interface IAPIError {
|
||||
errMsg: string
|
||||
}
|
||||
|
||||
interface IAPIParam<T = any> {
|
||||
config?: ICloudConfig
|
||||
success?: (res: T) => void
|
||||
fail?: (err: IAPIError) => void
|
||||
complete?: (val: T | IAPIError) => void
|
||||
}
|
||||
|
||||
interface IAPISuccessParam {
|
||||
errMsg: string
|
||||
}
|
||||
|
||||
type IAPICompleteParam = IAPISuccessParam | IAPIError
|
||||
|
||||
type IAPIFunction<T, P extends IAPIParam<T>> = (param?: P) => Promise<T>
|
||||
|
||||
interface IInitCloudConfig {
|
||||
env?:
|
||||
| string
|
||||
| {
|
||||
database?: string
|
||||
functions?: string
|
||||
storage?: string
|
||||
}
|
||||
traceUser?: boolean
|
||||
}
|
||||
|
||||
interface ICloudConfig {
|
||||
env?: string
|
||||
traceUser?: boolean
|
||||
}
|
||||
|
||||
interface IICloudAPI {
|
||||
init: (config?: IInitCloudConfig) => void
|
||||
[api: string]: AnyFunction | IAPIFunction<any, any>
|
||||
}
|
||||
|
||||
interface ICloudService {
|
||||
name: string
|
||||
|
||||
getAPIs: () => { [name: string]: IAPIFunction<any, any> }
|
||||
}
|
||||
|
||||
interface ICloudServices {
|
||||
[serviceName: string]: ICloudService
|
||||
}
|
||||
|
||||
interface ICloudMetaData {
|
||||
session_id: string
|
||||
}
|
||||
|
||||
declare class InternalSymbol {}
|
||||
|
||||
interface AnyObject {
|
||||
[x: string]: any
|
||||
}
|
||||
|
||||
type AnyArray = any[]
|
||||
|
||||
type AnyFunction = (...args: any[]) => any
|
||||
|
||||
/**
|
||||
* extend wx with cloud
|
||||
*/
|
||||
interface WxCloud {
|
||||
init: (config?: ICloudConfig) => void
|
||||
|
||||
callFunction(param: OQ<ICloud.CallFunctionParam>): void
|
||||
callFunction(
|
||||
param: RQ<ICloud.CallFunctionParam>
|
||||
): Promise<ICloud.CallFunctionResult>
|
||||
|
||||
uploadFile(param: OQ<ICloud.UploadFileParam>): WechatMiniprogram.UploadTask
|
||||
uploadFile(
|
||||
param: RQ<ICloud.UploadFileParam>
|
||||
): Promise<ICloud.UploadFileResult>
|
||||
|
||||
downloadFile(
|
||||
param: OQ<ICloud.DownloadFileParam>
|
||||
): WechatMiniprogram.DownloadTask
|
||||
downloadFile(
|
||||
param: RQ<ICloud.DownloadFileParam>
|
||||
): Promise<ICloud.DownloadFileResult>
|
||||
|
||||
getTempFileURL(param: OQ<ICloud.GetTempFileURLParam>): void
|
||||
getTempFileURL(
|
||||
param: RQ<ICloud.GetTempFileURLParam>
|
||||
): Promise<ICloud.GetTempFileURLResult>
|
||||
|
||||
deleteFile(param: OQ<ICloud.DeleteFileParam>): void
|
||||
deleteFile(
|
||||
param: RQ<ICloud.DeleteFileParam>
|
||||
): Promise<ICloud.DeleteFileResult>
|
||||
|
||||
database: (config?: ICloudConfig) => DB.Database
|
||||
|
||||
CloudID: ICloud.ICloudIDConstructor
|
||||
CDN: ICloud.ICDNConstructor
|
||||
|
||||
callContainer(param: OQ<ICloud.CallContainerParam>): void
|
||||
callContainer(
|
||||
param: RQ<ICloud.CallContainerParam>
|
||||
): Promise<ICloud.CallContainerResult>
|
||||
|
||||
connectContainer(param: OQ<ICloud.ConnectContainerParam>): void
|
||||
connectContainer(
|
||||
param: RQ<ICloud.ConnectContainerParam>
|
||||
): Promise<ICloud.ConnectContainerResult>
|
||||
}
|
||||
|
||||
declare namespace ICloud {
|
||||
interface ICloudAPIParam<T = any> extends IAPIParam<T> {
|
||||
config?: ICloudConfig
|
||||
}
|
||||
|
||||
// === API: callFunction ===
|
||||
type CallFunctionData = AnyObject
|
||||
|
||||
interface CallFunctionResult extends IAPISuccessParam {
|
||||
result: AnyObject | string | undefined
|
||||
}
|
||||
|
||||
interface CallFunctionParam extends ICloudAPIParam<CallFunctionResult> {
|
||||
name: string
|
||||
data?: CallFunctionData
|
||||
slow?: boolean
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: container ===
|
||||
type CallContainerData = AnyObject
|
||||
|
||||
interface CallContainerResult extends IAPISuccessParam {
|
||||
data: any
|
||||
statusCode: number
|
||||
header: Record<string, any>
|
||||
callID: string
|
||||
}
|
||||
|
||||
interface CallContainerParam extends ICloudAPIParam<CallContainerResult> {
|
||||
path: string
|
||||
service?: string
|
||||
method?: string
|
||||
header?: Record<string, any>
|
||||
data?: any // string, object, ArrayBuffer
|
||||
dataType?: string
|
||||
responseType?: string
|
||||
timeout?: number
|
||||
verbose?: boolean
|
||||
followRedirect?: boolean
|
||||
}
|
||||
|
||||
interface ConnectContainerResult extends IAPISuccessParam {
|
||||
socketTask: WechatMiniprogram.SocketTask
|
||||
}
|
||||
|
||||
interface ConnectSocketOptions extends IAPIParam<void> {
|
||||
header?: Record<string, string>
|
||||
protocols?: string[]
|
||||
tcpNoDelay?: boolean
|
||||
perMessageDeflate?: boolean
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
type ConnectContainerParam = Omit<
|
||||
ConnectSocketOptions,
|
||||
'success' | 'fail' | 'complete'
|
||||
> &
|
||||
ICloudAPIParam<ConnectContainerResult> & {
|
||||
service: string
|
||||
path?: string
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: uploadFile ===
|
||||
interface UploadFileResult extends IAPISuccessParam {
|
||||
fileID: string
|
||||
statusCode: number
|
||||
}
|
||||
|
||||
interface UploadFileParam extends ICloudAPIParam<UploadFileResult> {
|
||||
cloudPath: string
|
||||
filePath: string
|
||||
header?: AnyObject
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: downloadFile ===
|
||||
interface DownloadFileResult extends IAPISuccessParam {
|
||||
tempFilePath: string
|
||||
statusCode: number
|
||||
}
|
||||
|
||||
interface DownloadFileParam extends ICloudAPIParam<DownloadFileResult> {
|
||||
fileID: string
|
||||
cloudPath?: string
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: getTempFileURL ===
|
||||
interface GetTempFileURLResult extends IAPISuccessParam {
|
||||
fileList: GetTempFileURLResultItem[]
|
||||
}
|
||||
|
||||
interface GetTempFileURLResultItem {
|
||||
fileID: string
|
||||
tempFileURL: string
|
||||
maxAge: number
|
||||
status: number
|
||||
errMsg: string
|
||||
}
|
||||
|
||||
interface GetTempFileURLParam extends ICloudAPIParam<GetTempFileURLResult> {
|
||||
fileList: string[]
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: deleteFile ===
|
||||
interface DeleteFileResult extends IAPISuccessParam {
|
||||
fileList: DeleteFileResultItem[]
|
||||
}
|
||||
|
||||
interface DeleteFileResultItem {
|
||||
fileID: string
|
||||
status: number
|
||||
errMsg: string
|
||||
}
|
||||
|
||||
interface DeleteFileParam extends ICloudAPIParam<DeleteFileResult> {
|
||||
fileList: string[]
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: CloudID ===
|
||||
abstract class CloudID {
|
||||
constructor(cloudID: string)
|
||||
}
|
||||
|
||||
interface ICloudIDConstructor {
|
||||
new (cloudId: string): CloudID
|
||||
(cloudId: string): CloudID
|
||||
}
|
||||
// === end ===
|
||||
|
||||
// === API: CDN ===
|
||||
abstract class CDN {
|
||||
target: string | ArrayBuffer | ICDNFilePathSpec
|
||||
constructor(target: string | ArrayBuffer | ICDNFilePathSpec)
|
||||
}
|
||||
|
||||
interface ICDNFilePathSpec {
|
||||
type: 'filePath'
|
||||
filePath: string
|
||||
}
|
||||
|
||||
interface ICDNConstructor {
|
||||
new (options: string | ArrayBuffer | ICDNFilePathSpec): CDN
|
||||
(options: string | ArrayBuffer | ICDNFilePathSpec): CDN
|
||||
}
|
||||
// === end ===
|
||||
}
|
||||
|
||||
// === Database ===
|
||||
declare namespace DB {
|
||||
/**
|
||||
* The class of all exposed cloud database instances
|
||||
*/
|
||||
class Database {
|
||||
readonly config: ICloudConfig
|
||||
readonly command: DatabaseCommand
|
||||
readonly Geo: IGeo
|
||||
readonly serverDate: () => ServerDate
|
||||
readonly RegExp: IRegExpConstructor
|
||||
|
||||
private constructor()
|
||||
|
||||
collection(collectionName: string): CollectionReference
|
||||
}
|
||||
|
||||
class CollectionReference extends Query {
|
||||
readonly collectionName: string
|
||||
|
||||
private constructor(name: string, database: Database)
|
||||
|
||||
doc(docId: string | number): DocumentReference
|
||||
|
||||
add(options: OQ<IAddDocumentOptions>): void
|
||||
add(options: RQ<IAddDocumentOptions>): Promise<IAddResult>
|
||||
}
|
||||
|
||||
class DocumentReference {
|
||||
private constructor(docId: string | number, database: Database)
|
||||
|
||||
field(object: Record<string, any>): this
|
||||
|
||||
get(options: OQ<IGetDocumentOptions>): void
|
||||
get(options?: RQ<IGetDocumentOptions>): Promise<IQuerySingleResult>
|
||||
|
||||
set(options: OQ<ISetSingleDocumentOptions>): void
|
||||
set(options?: RQ<ISetSingleDocumentOptions>): Promise<ISetResult>
|
||||
|
||||
update(options: OQ<IUpdateSingleDocumentOptions>): void
|
||||
update(
|
||||
options?: RQ<IUpdateSingleDocumentOptions>
|
||||
): Promise<IUpdateResult>
|
||||
|
||||
remove(options: OQ<IRemoveSingleDocumentOptions>): void
|
||||
remove(
|
||||
options?: RQ<IRemoveSingleDocumentOptions>
|
||||
): Promise<IRemoveResult>
|
||||
|
||||
watch(options: IWatchOptions): RealtimeListener
|
||||
}
|
||||
|
||||
class RealtimeListener {
|
||||
// "And Now His Watch Is Ended"
|
||||
close: () => Promise<void>
|
||||
}
|
||||
|
||||
class Query {
|
||||
where(condition: IQueryCondition): Query
|
||||
|
||||
orderBy(fieldPath: string, order: string): Query
|
||||
|
||||
limit(max: number): Query
|
||||
|
||||
skip(offset: number): Query
|
||||
|
||||
field(object: Record<string, any>): Query
|
||||
|
||||
get(options: OQ<IGetDocumentOptions>): void
|
||||
get(options?: RQ<IGetDocumentOptions>): Promise<IQueryResult>
|
||||
|
||||
count(options: OQ<ICountDocumentOptions>): void
|
||||
count(options?: RQ<ICountDocumentOptions>): Promise<ICountResult>
|
||||
|
||||
watch(options: IWatchOptions): RealtimeListener
|
||||
}
|
||||
|
||||
interface DatabaseCommand {
|
||||
eq(val: any): DatabaseQueryCommand
|
||||
neq(val: any): DatabaseQueryCommand
|
||||
gt(val: any): DatabaseQueryCommand
|
||||
gte(val: any): DatabaseQueryCommand
|
||||
lt(val: any): DatabaseQueryCommand
|
||||
lte(val: any): DatabaseQueryCommand
|
||||
in(val: any[]): DatabaseQueryCommand
|
||||
nin(val: any[]): DatabaseQueryCommand
|
||||
|
||||
geoNear(options: IGeoNearCommandOptions): DatabaseQueryCommand
|
||||
geoWithin(options: IGeoWithinCommandOptions): DatabaseQueryCommand
|
||||
geoIntersects(
|
||||
options: IGeoIntersectsCommandOptions
|
||||
): DatabaseQueryCommand
|
||||
|
||||
and(
|
||||
...expressions: Array<DatabaseLogicCommand | IQueryCondition>
|
||||
): DatabaseLogicCommand
|
||||
or(
|
||||
...expressions: Array<DatabaseLogicCommand | IQueryCondition>
|
||||
): DatabaseLogicCommand
|
||||
nor(
|
||||
...expressions: Array<DatabaseLogicCommand | IQueryCondition>
|
||||
): DatabaseLogicCommand
|
||||
not(expression: DatabaseLogicCommand): DatabaseLogicCommand
|
||||
|
||||
exists(val: boolean): DatabaseQueryCommand
|
||||
|
||||
mod(divisor: number, remainder: number): DatabaseQueryCommand
|
||||
|
||||
all(val: any[]): DatabaseQueryCommand
|
||||
elemMatch(val: any): DatabaseQueryCommand
|
||||
size(val: number): DatabaseQueryCommand
|
||||
|
||||
set(val: any): DatabaseUpdateCommand
|
||||
remove(): DatabaseUpdateCommand
|
||||
inc(val: number): DatabaseUpdateCommand
|
||||
mul(val: number): DatabaseUpdateCommand
|
||||
min(val: number): DatabaseUpdateCommand
|
||||
max(val: number): DatabaseUpdateCommand
|
||||
rename(val: string): DatabaseUpdateCommand
|
||||
bit(val: number): DatabaseUpdateCommand
|
||||
|
||||
push(...values: any[]): DatabaseUpdateCommand
|
||||
pop(): DatabaseUpdateCommand
|
||||
shift(): DatabaseUpdateCommand
|
||||
unshift(...values: any[]): DatabaseUpdateCommand
|
||||
addToSet(val: any): DatabaseUpdateCommand
|
||||
pull(val: any): DatabaseUpdateCommand
|
||||
pullAll(val: any): DatabaseUpdateCommand
|
||||
|
||||
project: {
|
||||
slice(val: number | [number, number]): DatabaseProjectionCommand
|
||||
}
|
||||
|
||||
aggregate: {
|
||||
__safe_props__?: Set<string>
|
||||
|
||||
abs(val: any): DatabaseAggregateCommand
|
||||
add(val: any): DatabaseAggregateCommand
|
||||
addToSet(val: any): DatabaseAggregateCommand
|
||||
allElementsTrue(val: any): DatabaseAggregateCommand
|
||||
and(val: any): DatabaseAggregateCommand
|
||||
anyElementTrue(val: any): DatabaseAggregateCommand
|
||||
arrayElemAt(val: any): DatabaseAggregateCommand
|
||||
arrayToObject(val: any): DatabaseAggregateCommand
|
||||
avg(val: any): DatabaseAggregateCommand
|
||||
ceil(val: any): DatabaseAggregateCommand
|
||||
cmp(val: any): DatabaseAggregateCommand
|
||||
concat(val: any): DatabaseAggregateCommand
|
||||
concatArrays(val: any): DatabaseAggregateCommand
|
||||
cond(val: any): DatabaseAggregateCommand
|
||||
convert(val: any): DatabaseAggregateCommand
|
||||
dateFromParts(val: any): DatabaseAggregateCommand
|
||||
dateToParts(val: any): DatabaseAggregateCommand
|
||||
dateFromString(val: any): DatabaseAggregateCommand
|
||||
dateToString(val: any): DatabaseAggregateCommand
|
||||
dayOfMonth(val: any): DatabaseAggregateCommand
|
||||
dayOfWeek(val: any): DatabaseAggregateCommand
|
||||
dayOfYear(val: any): DatabaseAggregateCommand
|
||||
divide(val: any): DatabaseAggregateCommand
|
||||
eq(val: any): DatabaseAggregateCommand
|
||||
exp(val: any): DatabaseAggregateCommand
|
||||
filter(val: any): DatabaseAggregateCommand
|
||||
first(val: any): DatabaseAggregateCommand
|
||||
floor(val: any): DatabaseAggregateCommand
|
||||
gt(val: any): DatabaseAggregateCommand
|
||||
gte(val: any): DatabaseAggregateCommand
|
||||
hour(val: any): DatabaseAggregateCommand
|
||||
ifNull(val: any): DatabaseAggregateCommand
|
||||
in(val: any): DatabaseAggregateCommand
|
||||
indexOfArray(val: any): DatabaseAggregateCommand
|
||||
indexOfBytes(val: any): DatabaseAggregateCommand
|
||||
indexOfCP(val: any): DatabaseAggregateCommand
|
||||
isArray(val: any): DatabaseAggregateCommand
|
||||
isoDayOfWeek(val: any): DatabaseAggregateCommand
|
||||
isoWeek(val: any): DatabaseAggregateCommand
|
||||
isoWeekYear(val: any): DatabaseAggregateCommand
|
||||
last(val: any): DatabaseAggregateCommand
|
||||
let(val: any): DatabaseAggregateCommand
|
||||
literal(val: any): DatabaseAggregateCommand
|
||||
ln(val: any): DatabaseAggregateCommand
|
||||
log(val: any): DatabaseAggregateCommand
|
||||
log10(val: any): DatabaseAggregateCommand
|
||||
lt(val: any): DatabaseAggregateCommand
|
||||
lte(val: any): DatabaseAggregateCommand
|
||||
ltrim(val: any): DatabaseAggregateCommand
|
||||
map(val: any): DatabaseAggregateCommand
|
||||
max(val: any): DatabaseAggregateCommand
|
||||
mergeObjects(val: any): DatabaseAggregateCommand
|
||||
meta(val: any): DatabaseAggregateCommand
|
||||
min(val: any): DatabaseAggregateCommand
|
||||
millisecond(val: any): DatabaseAggregateCommand
|
||||
minute(val: any): DatabaseAggregateCommand
|
||||
mod(val: any): DatabaseAggregateCommand
|
||||
month(val: any): DatabaseAggregateCommand
|
||||
multiply(val: any): DatabaseAggregateCommand
|
||||
neq(val: any): DatabaseAggregateCommand
|
||||
not(val: any): DatabaseAggregateCommand
|
||||
objectToArray(val: any): DatabaseAggregateCommand
|
||||
or(val: any): DatabaseAggregateCommand
|
||||
pow(val: any): DatabaseAggregateCommand
|
||||
push(val: any): DatabaseAggregateCommand
|
||||
range(val: any): DatabaseAggregateCommand
|
||||
reduce(val: any): DatabaseAggregateCommand
|
||||
reverseArray(val: any): DatabaseAggregateCommand
|
||||
rtrim(val: any): DatabaseAggregateCommand
|
||||
second(val: any): DatabaseAggregateCommand
|
||||
setDifference(val: any): DatabaseAggregateCommand
|
||||
setEquals(val: any): DatabaseAggregateCommand
|
||||
setIntersection(val: any): DatabaseAggregateCommand
|
||||
setIsSubset(val: any): DatabaseAggregateCommand
|
||||
setUnion(val: any): DatabaseAggregateCommand
|
||||
size(val: any): DatabaseAggregateCommand
|
||||
slice(val: any): DatabaseAggregateCommand
|
||||
split(val: any): DatabaseAggregateCommand
|
||||
sqrt(val: any): DatabaseAggregateCommand
|
||||
stdDevPop(val: any): DatabaseAggregateCommand
|
||||
stdDevSamp(val: any): DatabaseAggregateCommand
|
||||
strcasecmp(val: any): DatabaseAggregateCommand
|
||||
strLenBytes(val: any): DatabaseAggregateCommand
|
||||
strLenCP(val: any): DatabaseAggregateCommand
|
||||
substr(val: any): DatabaseAggregateCommand
|
||||
substrBytes(val: any): DatabaseAggregateCommand
|
||||
substrCP(val: any): DatabaseAggregateCommand
|
||||
subtract(val: any): DatabaseAggregateCommand
|
||||
sum(val: any): DatabaseAggregateCommand
|
||||
switch(val: any): DatabaseAggregateCommand
|
||||
toBool(val: any): DatabaseAggregateCommand
|
||||
toDate(val: any): DatabaseAggregateCommand
|
||||
toDecimal(val: any): DatabaseAggregateCommand
|
||||
toDouble(val: any): DatabaseAggregateCommand
|
||||
toInt(val: any): DatabaseAggregateCommand
|
||||
toLong(val: any): DatabaseAggregateCommand
|
||||
toObjectId(val: any): DatabaseAggregateCommand
|
||||
toString(val: any): DatabaseAggregateCommand
|
||||
toLower(val: any): DatabaseAggregateCommand
|
||||
toUpper(val: any): DatabaseAggregateCommand
|
||||
trim(val: any): DatabaseAggregateCommand
|
||||
trunc(val: any): DatabaseAggregateCommand
|
||||
type(val: any): DatabaseAggregateCommand
|
||||
week(val: any): DatabaseAggregateCommand
|
||||
year(val: any): DatabaseAggregateCommand
|
||||
zip(val: any): DatabaseAggregateCommand
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseAggregateCommand {}
|
||||
|
||||
enum LOGIC_COMMANDS_LITERAL {
|
||||
AND = 'and',
|
||||
OR = 'or',
|
||||
NOT = 'not',
|
||||
NOR = 'nor'
|
||||
}
|
||||
|
||||
class DatabaseLogicCommand {
|
||||
and(...expressions: DatabaseLogicCommand[]): DatabaseLogicCommand
|
||||
or(...expressions: DatabaseLogicCommand[]): DatabaseLogicCommand
|
||||
nor(...expressions: DatabaseLogicCommand[]): DatabaseLogicCommand
|
||||
not(expression: DatabaseLogicCommand): DatabaseLogicCommand
|
||||
}
|
||||
|
||||
enum QUERY_COMMANDS_LITERAL {
|
||||
// comparison
|
||||
EQ = 'eq',
|
||||
NEQ = 'neq',
|
||||
GT = 'gt',
|
||||
GTE = 'gte',
|
||||
LT = 'lt',
|
||||
LTE = 'lte',
|
||||
IN = 'in',
|
||||
NIN = 'nin',
|
||||
// geo
|
||||
GEO_NEAR = 'geoNear',
|
||||
GEO_WITHIN = 'geoWithin',
|
||||
GEO_INTERSECTS = 'geoIntersects',
|
||||
// element
|
||||
EXISTS = 'exists',
|
||||
// evaluation
|
||||
MOD = 'mod',
|
||||
// array
|
||||
ALL = 'all',
|
||||
ELEM_MATCH = 'elemMatch',
|
||||
SIZE = 'size'
|
||||
}
|
||||
|
||||
class DatabaseQueryCommand extends DatabaseLogicCommand {
|
||||
eq(val: any): DatabaseLogicCommand
|
||||
neq(val: any): DatabaseLogicCommand
|
||||
gt(val: any): DatabaseLogicCommand
|
||||
gte(val: any): DatabaseLogicCommand
|
||||
lt(val: any): DatabaseLogicCommand
|
||||
lte(val: any): DatabaseLogicCommand
|
||||
in(val: any[]): DatabaseLogicCommand
|
||||
nin(val: any[]): DatabaseLogicCommand
|
||||
|
||||
exists(val: boolean): DatabaseLogicCommand
|
||||
|
||||
mod(divisor: number, remainder: number): DatabaseLogicCommand
|
||||
|
||||
all(val: any[]): DatabaseLogicCommand
|
||||
elemMatch(val: any): DatabaseLogicCommand
|
||||
size(val: number): DatabaseLogicCommand
|
||||
|
||||
geoNear(options: IGeoNearCommandOptions): DatabaseLogicCommand
|
||||
geoWithin(options: IGeoWithinCommandOptions): DatabaseLogicCommand
|
||||
geoIntersects(
|
||||
options: IGeoIntersectsCommandOptions
|
||||
): DatabaseLogicCommand
|
||||
}
|
||||
|
||||
enum PROJECTION_COMMANDS_LITERAL {
|
||||
SLICE = 'slice'
|
||||
}
|
||||
|
||||
class DatabaseProjectionCommand {}
|
||||
|
||||
enum UPDATE_COMMANDS_LITERAL {
|
||||
// field
|
||||
SET = 'set',
|
||||
REMOVE = 'remove',
|
||||
INC = 'inc',
|
||||
MUL = 'mul',
|
||||
MIN = 'min',
|
||||
MAX = 'max',
|
||||
RENAME = 'rename',
|
||||
// bitwise
|
||||
BIT = 'bit',
|
||||
// array
|
||||
PUSH = 'push',
|
||||
POP = 'pop',
|
||||
SHIFT = 'shift',
|
||||
UNSHIFT = 'unshift',
|
||||
ADD_TO_SET = 'addToSet',
|
||||
PULL = 'pull',
|
||||
PULL_ALL = 'pullAll'
|
||||
}
|
||||
|
||||
class DatabaseUpdateCommand {}
|
||||
|
||||
class Batch {}
|
||||
|
||||
/**
|
||||
* A contract that all API provider must adhere to
|
||||
*/
|
||||
class APIBaseContract<
|
||||
PromiseReturn,
|
||||
CallbackReturn,
|
||||
Param extends IAPIParam,
|
||||
Context = any
|
||||
> {
|
||||
getContext(param: Param): Context
|
||||
|
||||
/**
|
||||
* In case of callback-style invocation, this function will be called
|
||||
*/
|
||||
getCallbackReturn(param: Param, context: Context): CallbackReturn
|
||||
|
||||
getFinalParam<T extends Param>(param: Param, context: Context): T
|
||||
|
||||
run<T extends Param>(param: T): Promise<PromiseReturn>
|
||||
}
|
||||
|
||||
interface IGeoPointConstructor {
|
||||
new (longitude: number, latitide: number): GeoPoint
|
||||
new (geojson: IGeoJSONPoint): GeoPoint
|
||||
(longitude: number, latitide: number): GeoPoint
|
||||
(geojson: IGeoJSONPoint): GeoPoint
|
||||
}
|
||||
|
||||
interface IGeoMultiPointConstructor {
|
||||
new (points: GeoPoint[] | IGeoJSONMultiPoint): GeoMultiPoint
|
||||
(points: GeoPoint[] | IGeoJSONMultiPoint): GeoMultiPoint
|
||||
}
|
||||
|
||||
interface IGeoLineStringConstructor {
|
||||
new (points: GeoPoint[] | IGeoJSONLineString): GeoLineString
|
||||
(points: GeoPoint[] | IGeoJSONLineString): GeoLineString
|
||||
}
|
||||
|
||||
interface IGeoMultiLineStringConstructor {
|
||||
new (
|
||||
lineStrings: GeoLineString[] | IGeoJSONMultiLineString
|
||||
): GeoMultiLineString
|
||||
(
|
||||
lineStrings: GeoLineString[] | IGeoJSONMultiLineString
|
||||
): GeoMultiLineString
|
||||
}
|
||||
|
||||
interface IGeoPolygonConstructor {
|
||||
new (lineStrings: GeoLineString[] | IGeoJSONPolygon): GeoPolygon
|
||||
(lineStrings: GeoLineString[] | IGeoJSONPolygon): GeoPolygon
|
||||
}
|
||||
|
||||
interface IGeoMultiPolygonConstructor {
|
||||
new (polygons: GeoPolygon[] | IGeoJSONMultiPolygon): GeoMultiPolygon
|
||||
(polygons: GeoPolygon[] | IGeoJSONMultiPolygon): GeoMultiPolygon
|
||||
}
|
||||
|
||||
interface IGeo {
|
||||
Point: IGeoPointConstructor
|
||||
MultiPoint: IGeoMultiPointConstructor
|
||||
LineString: IGeoLineStringConstructor
|
||||
MultiLineString: IGeoMultiLineStringConstructor
|
||||
Polygon: IGeoPolygonConstructor
|
||||
MultiPolygon: IGeoMultiPolygonConstructor
|
||||
}
|
||||
|
||||
interface IGeoJSONPoint {
|
||||
type: 'Point'
|
||||
coordinates: [number, number]
|
||||
}
|
||||
|
||||
interface IGeoJSONMultiPoint {
|
||||
type: 'MultiPoint'
|
||||
coordinates: Array<[number, number]>
|
||||
}
|
||||
|
||||
interface IGeoJSONLineString {
|
||||
type: 'LineString'
|
||||
coordinates: Array<[number, number]>
|
||||
}
|
||||
|
||||
interface IGeoJSONMultiLineString {
|
||||
type: 'MultiLineString'
|
||||
coordinates: Array<Array<[number, number]>>
|
||||
}
|
||||
|
||||
interface IGeoJSONPolygon {
|
||||
type: 'Polygon'
|
||||
coordinates: Array<Array<[number, number]>>
|
||||
}
|
||||
|
||||
interface IGeoJSONMultiPolygon {
|
||||
type: 'MultiPolygon'
|
||||
coordinates: Array<Array<Array<[number, number]>>>
|
||||
}
|
||||
|
||||
type IGeoJSONObject =
|
||||
| IGeoJSONPoint
|
||||
| IGeoJSONMultiPoint
|
||||
| IGeoJSONLineString
|
||||
| IGeoJSONMultiLineString
|
||||
| IGeoJSONPolygon
|
||||
| IGeoJSONMultiPolygon
|
||||
|
||||
abstract class GeoPoint {
|
||||
longitude: number
|
||||
latitude: number
|
||||
|
||||
constructor(longitude: number, latitude: number)
|
||||
|
||||
toJSON(): Record<string, any>
|
||||
toString(): string
|
||||
}
|
||||
|
||||
abstract class GeoMultiPoint {
|
||||
points: GeoPoint[]
|
||||
|
||||
constructor(points: GeoPoint[])
|
||||
|
||||
toJSON(): IGeoJSONMultiPoint
|
||||
toString(): string
|
||||
}
|
||||
|
||||
abstract class GeoLineString {
|
||||
points: GeoPoint[]
|
||||
|
||||
constructor(points: GeoPoint[])
|
||||
|
||||
toJSON(): IGeoJSONLineString
|
||||
toString(): string
|
||||
}
|
||||
|
||||
abstract class GeoMultiLineString {
|
||||
lines: GeoLineString[]
|
||||
|
||||
constructor(lines: GeoLineString[])
|
||||
|
||||
toJSON(): IGeoJSONMultiLineString
|
||||
toString(): string
|
||||
}
|
||||
|
||||
abstract class GeoPolygon {
|
||||
lines: GeoLineString[]
|
||||
|
||||
constructor(lines: GeoLineString[])
|
||||
|
||||
toJSON(): IGeoJSONPolygon
|
||||
toString(): string
|
||||
}
|
||||
|
||||
abstract class GeoMultiPolygon {
|
||||
polygons: GeoPolygon[]
|
||||
|
||||
constructor(polygons: GeoPolygon[])
|
||||
|
||||
toJSON(): IGeoJSONMultiPolygon
|
||||
toString(): string
|
||||
}
|
||||
|
||||
type GeoInstance =
|
||||
| GeoPoint
|
||||
| GeoMultiPoint
|
||||
| GeoLineString
|
||||
| GeoMultiLineString
|
||||
| GeoPolygon
|
||||
| GeoMultiPolygon
|
||||
|
||||
interface IGeoNearCommandOptions {
|
||||
geometry: GeoPoint
|
||||
maxDistance?: number
|
||||
minDistance?: number
|
||||
}
|
||||
|
||||
interface IGeoWithinCommandOptions {
|
||||
geometry: GeoPolygon | GeoMultiPolygon
|
||||
}
|
||||
|
||||
interface IGeoIntersectsCommandOptions {
|
||||
geometry:
|
||||
| GeoPoint
|
||||
| GeoMultiPoint
|
||||
| GeoLineString
|
||||
| GeoMultiLineString
|
||||
| GeoPolygon
|
||||
| GeoMultiPolygon
|
||||
}
|
||||
|
||||
interface IServerDateOptions {
|
||||
offset: number
|
||||
}
|
||||
|
||||
abstract class ServerDate {
|
||||
readonly options: IServerDateOptions
|
||||
constructor(options?: IServerDateOptions)
|
||||
}
|
||||
|
||||
interface IRegExpOptions {
|
||||
regexp: string
|
||||
options?: string
|
||||
}
|
||||
|
||||
interface IRegExpConstructor {
|
||||
new (options: IRegExpOptions): RegExp
|
||||
(options: IRegExpOptions): RegExp
|
||||
}
|
||||
|
||||
abstract class RegExp {
|
||||
readonly regexp: string
|
||||
readonly options: string
|
||||
constructor(options: IRegExpOptions)
|
||||
}
|
||||
|
||||
type DocumentId = string | number
|
||||
|
||||
interface IDocumentData {
|
||||
_id?: DocumentId
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type IDBAPIParam = IAPIParam
|
||||
|
||||
interface IAddDocumentOptions extends IDBAPIParam {
|
||||
data: IDocumentData
|
||||
}
|
||||
|
||||
type IGetDocumentOptions = IDBAPIParam
|
||||
|
||||
type ICountDocumentOptions = IDBAPIParam
|
||||
|
||||
interface IUpdateDocumentOptions extends IDBAPIParam {
|
||||
data: IUpdateCondition
|
||||
}
|
||||
|
||||
interface IUpdateSingleDocumentOptions extends IDBAPIParam {
|
||||
data: IUpdateCondition
|
||||
}
|
||||
|
||||
interface ISetDocumentOptions extends IDBAPIParam {
|
||||
data: IUpdateCondition
|
||||
}
|
||||
|
||||
interface ISetSingleDocumentOptions extends IDBAPIParam {
|
||||
data: IUpdateCondition
|
||||
}
|
||||
|
||||
interface IRemoveDocumentOptions extends IDBAPIParam {
|
||||
query: IQueryCondition
|
||||
}
|
||||
|
||||
type IRemoveSingleDocumentOptions = IDBAPIParam
|
||||
|
||||
interface IWatchOptions {
|
||||
// server realtime data init & change event
|
||||
onChange: (snapshot: ISnapshot) => void
|
||||
// error while connecting / listening
|
||||
onError: (error: any) => void
|
||||
}
|
||||
|
||||
interface ISnapshot {
|
||||
id: number
|
||||
docChanges: ISingleDBEvent[]
|
||||
docs: Record<string, any>
|
||||
type?: SnapshotType
|
||||
}
|
||||
|
||||
type SnapshotType = 'init'
|
||||
|
||||
interface ISingleDBEvent {
|
||||
id: number
|
||||
dataType: DataType
|
||||
queueType: QueueType
|
||||
docId: string
|
||||
doc: Record<string, any>
|
||||
updatedFields?: Record<string, any>
|
||||
removedFields?: string[]
|
||||
}
|
||||
|
||||
type DataType = 'init' | 'update' | 'replace' | 'add' | 'remove' | 'limit'
|
||||
|
||||
type QueueType = 'init' | 'enqueue' | 'dequeue' | 'update'
|
||||
|
||||
interface IQueryCondition {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type IStringQueryCondition = string
|
||||
|
||||
interface IQueryResult extends IAPISuccessParam {
|
||||
data: IDocumentData[]
|
||||
}
|
||||
|
||||
interface IQuerySingleResult extends IAPISuccessParam {
|
||||
data: IDocumentData
|
||||
}
|
||||
|
||||
interface IUpdateCondition {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type IStringUpdateCondition = string
|
||||
|
||||
interface IAddResult extends IAPISuccessParam {
|
||||
_id: DocumentId
|
||||
}
|
||||
|
||||
interface IUpdateResult extends IAPISuccessParam {
|
||||
stats: {
|
||||
updated: number
|
||||
// created: number,
|
||||
}
|
||||
}
|
||||
|
||||
interface ISetResult extends IAPISuccessParam {
|
||||
_id: DocumentId
|
||||
stats: {
|
||||
updated: number
|
||||
created: number
|
||||
}
|
||||
}
|
||||
|
||||
interface IRemoveResult extends IAPISuccessParam {
|
||||
stats: {
|
||||
removed: number
|
||||
}
|
||||
}
|
||||
|
||||
interface ICountResult extends IAPISuccessParam {
|
||||
total: number
|
||||
}
|
||||
}
|
||||
|
||||
type Optional<T> = { [K in keyof T]+?: T[K] }
|
||||
|
||||
type OQ<
|
||||
T extends Optional<
|
||||
Record<'complete' | 'success' | 'fail', (...args: any[]) => any>
|
||||
>
|
||||
> =
|
||||
| (RQ<T> & Required<Pick<T, 'success'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'fail'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'complete'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'success' | 'fail'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'success' | 'complete'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'fail' | 'complete'>>)
|
||||
| (RQ<T> & Required<Pick<T, 'fail' | 'complete' | 'success'>>)
|
||||
|
||||
type RQ<
|
||||
T extends Optional<
|
||||
Record<'complete' | 'success' | 'fail', (...args: any[]) => any>
|
||||
>
|
||||
> = Pick<T, Exclude<keyof T, 'complete' | 'success' | 'fail'>>
|
|
@ -0,0 +1,676 @@
|
|||
/*! *****************************************************************************
|
||||
Copyright (c) 2023 Tencent, Inc. All rights reserved.
|
||||
|
||||
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.
|
||||
***************************************************************************** */
|
||||
|
||||
declare namespace WechatMiniprogram.Component {
|
||||
type Instance<
|
||||
TData extends DataOption,
|
||||
TProperty extends PropertyOption,
|
||||
TMethod extends Partial<MethodOption>,
|
||||
TCustomInstanceProperty extends IAnyObject = {},
|
||||
TIsPage extends boolean = false
|
||||
> = InstanceProperties &
|
||||
InstanceMethods<TData> &
|
||||
TMethod &
|
||||
(TIsPage extends true ? Page.ILifetime : {}) &
|
||||
TCustomInstanceProperty & {
|
||||
/** 组件数据,**包括内部数据和属性值** */
|
||||
data: TData & PropertyOptionToData<TProperty>
|
||||
/** 组件数据,**包括内部数据和属性值**(与 `data` 一致) */
|
||||
properties: TData & PropertyOptionToData<TProperty>
|
||||
}
|
||||
type TrivialInstance = Instance<
|
||||
IAnyObject,
|
||||
IAnyObject,
|
||||
IAnyObject,
|
||||
IAnyObject
|
||||
>
|
||||
type TrivialOption = Options<IAnyObject, IAnyObject, IAnyObject, IAnyObject>
|
||||
type Options<
|
||||
TData extends DataOption,
|
||||
TProperty extends PropertyOption,
|
||||
TMethod extends MethodOption,
|
||||
TCustomInstanceProperty extends IAnyObject = {},
|
||||
TIsPage extends boolean = false
|
||||
> = Partial<Data<TData>> &
|
||||
Partial<Property<TProperty>> &
|
||||
Partial<Method<TMethod, TIsPage>> &
|
||||
Partial<OtherOption> &
|
||||
Partial<Lifetimes> &
|
||||
ThisType<
|
||||
Instance<
|
||||
TData,
|
||||
TProperty,
|
||||
TMethod,
|
||||
TCustomInstanceProperty,
|
||||
TIsPage
|
||||
>
|
||||
>
|
||||
interface Constructor {
|
||||
<
|
||||
TData extends DataOption,
|
||||
TProperty extends PropertyOption,
|
||||
TMethod extends MethodOption,
|
||||
TCustomInstanceProperty extends IAnyObject = {},
|
||||
TIsPage extends boolean = false
|
||||
>(
|
||||
options: Options<
|
||||
TData,
|
||||
TProperty,
|
||||
TMethod,
|
||||
TCustomInstanceProperty,
|
||||
TIsPage
|
||||
>
|
||||
): string
|
||||
}
|
||||
type DataOption = Record<string, any>
|
||||
type PropertyOption = Record<string, AllProperty>
|
||||
type MethodOption = Record<string, Function>
|
||||
|
||||
interface Data<D extends DataOption> {
|
||||
/** 组件的内部数据,和 `properties` 一同用于组件的模板渲染 */
|
||||
data?: D
|
||||
}
|
||||
interface Property<P extends PropertyOption> {
|
||||
/** 组件的对外属性,是属性名到属性设置的映射表 */
|
||||
properties: P
|
||||
}
|
||||
interface Method<M extends MethodOption, TIsPage extends boolean = false> {
|
||||
/** 组件的方法,包括事件响应函数和任意的自定义方法,关于事件响应函数的使用,参见 [组件间通信与事件](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html) */
|
||||
methods: M & (TIsPage extends true ? Partial<Page.ILifetime> : {})
|
||||
}
|
||||
type PropertyType =
|
||||
| StringConstructor
|
||||
| NumberConstructor
|
||||
| BooleanConstructor
|
||||
| ArrayConstructor
|
||||
| ObjectConstructor
|
||||
| null
|
||||
type ValueType<T extends PropertyType> = T extends null
|
||||
? any
|
||||
: T extends StringConstructor
|
||||
? string
|
||||
: T extends NumberConstructor
|
||||
? number
|
||||
: T extends BooleanConstructor
|
||||
? boolean
|
||||
: T extends ArrayConstructor
|
||||
? any[]
|
||||
: T extends ObjectConstructor
|
||||
? IAnyObject
|
||||
: never
|
||||
type FullProperty<T extends PropertyType> = {
|
||||
/** 属性类型 */
|
||||
type: T
|
||||
/** 属性初始值 */
|
||||
value?: ValueType<T>
|
||||
/** 属性值被更改时的响应函数 */
|
||||
observer?:
|
||||
| string
|
||||
| ((
|
||||
newVal: ValueType<T>,
|
||||
oldVal: ValueType<T>,
|
||||
changedPath: Array<string | number>
|
||||
) => void)
|
||||
/** 属性的类型(可以指定多个) */
|
||||
optionalTypes?: ShortProperty[]
|
||||
}
|
||||
type AllFullProperty =
|
||||
| FullProperty<StringConstructor>
|
||||
| FullProperty<NumberConstructor>
|
||||
| FullProperty<BooleanConstructor>
|
||||
| FullProperty<ArrayConstructor>
|
||||
| FullProperty<ObjectConstructor>
|
||||
| FullProperty<null>
|
||||
type ShortProperty =
|
||||
| StringConstructor
|
||||
| NumberConstructor
|
||||
| BooleanConstructor
|
||||
| ArrayConstructor
|
||||
| ObjectConstructor
|
||||
| null
|
||||
type AllProperty = AllFullProperty | ShortProperty
|
||||
type PropertyToData<T extends AllProperty> = T extends ShortProperty
|
||||
? ValueType<T>
|
||||
: FullPropertyToData<Exclude<T, ShortProperty>>
|
||||
type FullPropertyToData<T extends AllFullProperty> = ValueType<T['type']>
|
||||
// type FullPropertyToData<T extends AllFullProperty> = unknown extends T['value'] ? ValueType<T['type']> : T['value']
|
||||
type PropertyOptionToData<P extends PropertyOption> = {
|
||||
[name in keyof P]: PropertyToData<P[name]>
|
||||
}
|
||||
|
||||
interface InstanceProperties {
|
||||
/** 组件的文件路径 */
|
||||
is: string
|
||||
/** 节点id */
|
||||
id: string
|
||||
/** 节点dataset */
|
||||
dataset: Record<string, string>
|
||||
}
|
||||
|
||||
interface InstanceMethods<D extends DataOption> {
|
||||
/** `setData` 函数用于将数据从逻辑层发送到视图层
|
||||
*(异步),同时改变对应的 `this.data` 的值(同步)。
|
||||
*
|
||||
* **注意:**
|
||||
*
|
||||
* 1. **直接修改 this.data 而不调用 this.setData 是无法改变页面的状态的,还会造成数据不一致**。
|
||||
* 1. 仅支持设置可 JSON 化的数据。
|
||||
* 1. 单次设置的数据不能超过1024kB,请尽量避免一次设置过多的数据。
|
||||
* 1. 请不要把 data 中任何一项的 value 设为 `undefined` ,否则这一项将不被设置并可能遗留一些潜在问题。
|
||||
*/
|
||||
setData(
|
||||
/** 这次要改变的数据
|
||||
*
|
||||
* 以 `key: value` 的形式表示,将 `this.data` 中的 `key` 对应的值改变成 `value`。
|
||||
*
|
||||
* 其中 `key` 可以以数据路径的形式给出,支持改变数组中的某一项或对象的某个属性,如 `array[2].message`,`a.b.c.d`,并且不需要在 this.data 中预先定义。
|
||||
*/
|
||||
data: Partial<D> & IAnyObject,
|
||||
/** setData引起的界面更新渲染完毕后的回调函数,最低基础库: `1.5.0` */
|
||||
callback?: () => void
|
||||
): void
|
||||
|
||||
/** 检查组件是否具有 `behavior` (检查时会递归检查被直接或间接引入的所有behavior) */
|
||||
hasBehavior(behavior: Behavior.BehaviorIdentifier): void
|
||||
/** 触发事件,参见组件事件 */
|
||||
triggerEvent<DetailType = any>(
|
||||
name: string,
|
||||
detail?: DetailType,
|
||||
options?: TriggerEventOption
|
||||
): void
|
||||
/** 创建一个 SelectorQuery 对象,选择器选取范围为这个组件实例内 */
|
||||
createSelectorQuery(): SelectorQuery
|
||||
/** 创建一个 IntersectionObserver 对象,选择器选取范围为这个组件实例内 */
|
||||
createIntersectionObserver(
|
||||
options: CreateIntersectionObserverOption
|
||||
): IntersectionObserver
|
||||
/** 使用选择器选择组件实例节点,返回匹配到的第一个组件实例对象(会被 `wx://component-export` 影响) */
|
||||
selectComponent(selector: string): TrivialInstance
|
||||
/** 使用选择器选择组件实例节点,返回匹配到的全部组件实例对象组成的数组 */
|
||||
selectAllComponents(selector: string): TrivialInstance[]
|
||||
/**
|
||||
* 选取当前组件节点所在的组件实例(即组件的引用者),返回它的组件实例对象(会被 `wx://component-export` 影响)
|
||||
*
|
||||
* 最低基础库版本:[`2.8.2`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
**/
|
||||
selectOwnerComponent(): TrivialInstance
|
||||
/** 获取这个关系所对应的所有关联节点,参见 组件间关系 */
|
||||
getRelationNodes(relationKey: string): TrivialInstance[]
|
||||
/**
|
||||
* 立刻执行 callback ,其中的多个 setData 之间不会触发界面绘制(只有某些特殊场景中需要,如用于在不同组件同时 setData 时进行界面绘制同步)
|
||||
*
|
||||
* 最低基础库版本:[`2.4.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
**/
|
||||
groupSetData(callback?: () => void): void
|
||||
/**
|
||||
* 返回当前页面的 custom-tab-bar 的组件实例
|
||||
*
|
||||
* 最低基础库版本:[`2.6.2`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
**/
|
||||
getTabBar(): TrivialInstance
|
||||
/**
|
||||
* 返回页面标识符(一个字符串),可以用来判断几个自定义组件实例是不是在同一个页面内
|
||||
*
|
||||
* 最低基础库版本:[`2.7.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
**/
|
||||
getPageId(): string
|
||||
/**
|
||||
* 执行关键帧动画,详见[动画](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
|
||||
*
|
||||
* 最低基础库版本:[`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
**/
|
||||
animate(
|
||||
selector: string,
|
||||
keyFrames: KeyFrame[],
|
||||
duration: number,
|
||||
callback?: () => void
|
||||
): void
|
||||
/**
|
||||
* 执行关键帧动画,详见[动画](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
|
||||
*
|
||||
* 最低基础库版本:[`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
**/
|
||||
animate(
|
||||
selector: string,
|
||||
keyFrames: ScrollTimelineKeyframe[],
|
||||
duration: number,
|
||||
scrollTimeline: ScrollTimelineOption
|
||||
): void
|
||||
/**
|
||||
* 清除关键帧动画,详见[动画](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
|
||||
*
|
||||
* 最低基础库版本:[`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
**/
|
||||
clearAnimation(selector: string, callback: () => void): void
|
||||
/**
|
||||
* 清除关键帧动画,详见[动画](https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html)
|
||||
*
|
||||
* 最低基础库版本:[`2.9.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
**/
|
||||
clearAnimation(
|
||||
selector: string,
|
||||
options?: ClearAnimationOptions,
|
||||
callback?: () => void
|
||||
): void
|
||||
/**
|
||||
* 当从另一页面跳转到该页面时,获得与来源页面实例通信当事件通道,详见 [wx.navigateTo]((wx.navigateTo))
|
||||
*
|
||||
* 最低基础库版本:[`2.7.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
getOpenerEventChannel(): EventChannel
|
||||
/**
|
||||
* 获取更新性能统计信息,详见 [获取更新性能统计信息]((custom-component/update-perf-stat))
|
||||
*
|
||||
*
|
||||
* 最低基础库版本:[`2.12.0`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
setUpdatePerformanceListener<WithDataPath extends boolean = false>(
|
||||
options: SetUpdatePerformanceListenerOption<WithDataPath>,
|
||||
callback?: UpdatePerformanceListener<WithDataPath>
|
||||
): void
|
||||
}
|
||||
|
||||
interface ComponentOptions {
|
||||
/**
|
||||
* [启用多slot支持](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#组件wxml的slot)
|
||||
*/
|
||||
multipleSlots?: boolean
|
||||
/**
|
||||
* [组件样式隔离](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#组件样式隔离)
|
||||
*/
|
||||
addGlobalClass?: boolean
|
||||
/**
|
||||
* [组件样式隔离](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#组件样式隔离)
|
||||
*/
|
||||
styleIsolation?:
|
||||
| 'isolated'
|
||||
| 'apply-shared'
|
||||
| 'shared'
|
||||
| 'page-isolated'
|
||||
| 'page-apply-shared'
|
||||
| 'page-shared'
|
||||
/**
|
||||
* [纯数据字段](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/pure-data.html) 是一些不用于界面渲染的 data 字段,可以用于提升页面更新性能。从小程序基础库版本 2.8.2 开始支持。
|
||||
*/
|
||||
pureDataPattern?: RegExp
|
||||
/**
|
||||
* [虚拟化组件节点](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#%E8%99%9A%E6%8B%9F%E5%8C%96%E7%BB%84%E4%BB%B6%E8%8A%82%E7%82%B9) 使自定义组件内部的第一层节点由自定义组件本身完全决定。从小程序基础库版本 [`2.11.2`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) 开始支持 */
|
||||
virtualHost?: boolean
|
||||
}
|
||||
|
||||
interface TriggerEventOption {
|
||||
/** 事件是否冒泡
|
||||
*
|
||||
* 默认值: `false`
|
||||
*/
|
||||
bubbles?: boolean
|
||||
/** 事件是否可以穿越组件边界,为false时,事件将只能在引用组件的节点树上触发,不进入其他任何组件内部
|
||||
*
|
||||
* 默认值: `false`
|
||||
*/
|
||||
composed?: boolean
|
||||
/** 事件是否拥有捕获阶段
|
||||
*
|
||||
* 默认值: `false`
|
||||
*/
|
||||
capturePhase?: boolean
|
||||
}
|
||||
|
||||
interface RelationOption {
|
||||
/** 目标组件的相对关系 */
|
||||
type: 'parent' | 'child' | 'ancestor' | 'descendant'
|
||||
/** 关系生命周期函数,当关系被建立在页面节点树中时触发,触发时机在组件attached生命周期之后 */
|
||||
linked?(target: TrivialInstance): void
|
||||
/** 关系生命周期函数,当关系在页面节点树中发生改变时触发,触发时机在组件moved生命周期之后 */
|
||||
linkChanged?(target: TrivialInstance): void
|
||||
/** 关系生命周期函数,当关系脱离页面节点树时触发,触发时机在组件detached生命周期之后 */
|
||||
unlinked?(target: TrivialInstance): void
|
||||
/** 如果这一项被设置,则它表示关联的目标节点所应具有的behavior,所有拥有这一behavior的组件节点都会被关联 */
|
||||
target?: string
|
||||
}
|
||||
|
||||
interface PageLifetimes {
|
||||
/** 页面生命周期回调—监听页面显示
|
||||
*
|
||||
* 页面显示/切入前台时触发。
|
||||
*/
|
||||
show(): void
|
||||
/** 页面生命周期回调—监听页面隐藏
|
||||
*
|
||||
* 页面隐藏/切入后台时触发。 如 `navigateTo` 或底部 `tab` 切换到其他页面,小程序切入后台等。
|
||||
*/
|
||||
hide(): void
|
||||
/** 页面生命周期回调—监听页面尺寸变化
|
||||
*
|
||||
* 所在页面尺寸变化时执行
|
||||
*/
|
||||
resize(size: Page.IResizeOption): void
|
||||
}
|
||||
|
||||
type DefinitionFilter = <T extends TrivialOption>(
|
||||
/** 使用该 behavior 的 component/behavior 的定义对象 */
|
||||
defFields: T,
|
||||
/** 该 behavior 所使用的 behavior 的 definitionFilter 函数列表 */
|
||||
definitionFilterArr?: DefinitionFilter[]
|
||||
) => void
|
||||
|
||||
interface Lifetimes {
|
||||
/** 组件生命周期声明对象,组件的生命周期:`created`、`attached`、`ready`、`moved`、`detached` 将收归到 `lifetimes` 字段内进行声明,原有声明方式仍旧有效,如同时存在两种声明方式,则 `lifetimes` 字段内声明方式优先级最高
|
||||
*
|
||||
* 最低基础库: `2.2.3` */
|
||||
lifetimes: Partial<{
|
||||
/**
|
||||
* 在组件实例刚刚被创建时执行,注意此时不能调用 `setData`
|
||||
*
|
||||
* 最低基础库版本:[`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
created(): void
|
||||
/**
|
||||
* 在组件实例进入页面节点树时执行
|
||||
*
|
||||
* 最低基础库版本:[`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
attached(): void
|
||||
/**
|
||||
* 在组件在视图层布局完成后执行
|
||||
*
|
||||
* 最低基础库版本:[`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
ready(): void
|
||||
/**
|
||||
* 在组件实例被移动到节点树另一个位置时执行
|
||||
*
|
||||
* 最低基础库版本:[`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
moved(): void
|
||||
/**
|
||||
* 在组件实例被从页面节点树移除时执行
|
||||
*
|
||||
* 最低基础库版本:[`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
detached(): void
|
||||
/**
|
||||
* 每当组件方法抛出错误时执行
|
||||
*
|
||||
* 最低基础库版本:[`2.4.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
error(err: Error): void
|
||||
}>
|
||||
/**
|
||||
* @deprecated 旧式的定义方式,基础库 `2.2.3` 起请在 lifetimes 中定义
|
||||
*
|
||||
* 在组件实例刚刚被创建时执行
|
||||
*
|
||||
* 最低基础库版本:[`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
created(): void
|
||||
/**
|
||||
* @deprecated 旧式的定义方式,基础库 `2.2.3` 起请在 lifetimes 中定义
|
||||
*
|
||||
* 在组件实例进入页面节点树时执行
|
||||
*
|
||||
* 最低基础库版本:[`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
attached(): void
|
||||
/**
|
||||
* @deprecated 旧式的定义方式,基础库 `2.2.3` 起请在 lifetimes 中定义
|
||||
*
|
||||
* 在组件在视图层布局完成后执行
|
||||
*
|
||||
* 最低基础库版本:[`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
ready(): void
|
||||
/**
|
||||
* @deprecated 旧式的定义方式,基础库 `2.2.3` 起请在 lifetimes 中定义
|
||||
*
|
||||
* 在组件实例被移动到节点树另一个位置时执行
|
||||
*
|
||||
* 最低基础库版本:[`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
moved(): void
|
||||
/**
|
||||
* @deprecated 旧式的定义方式,基础库 `2.2.3` 起请在 lifetimes 中定义
|
||||
*
|
||||
* 在组件实例被从页面节点树移除时执行
|
||||
*
|
||||
* 最低基础库版本:[`1.6.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
detached(): void
|
||||
/**
|
||||
* @deprecated 旧式的定义方式,基础库 `2.2.3` 起请在 lifetimes 中定义
|
||||
*
|
||||
* 每当组件方法抛出错误时执行
|
||||
*
|
||||
* 最低基础库版本:[`2.4.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
error(err: Error): void
|
||||
}
|
||||
|
||||
interface OtherOption {
|
||||
/** 类似于mixins和traits的组件间代码复用机制,参见 [behaviors](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/behaviors.html) */
|
||||
behaviors: Behavior.BehaviorIdentifier[]
|
||||
/**
|
||||
* 组件数据字段监听器,用于监听 properties 和 data 的变化,参见 [数据监听器](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/observer.html)
|
||||
*
|
||||
* 最低基础库版本:[`2.6.1`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html)
|
||||
*/
|
||||
observers: Record<string, (...args: any[]) => any>
|
||||
/** 组件间关系定义,参见 [组件间关系](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html) */
|
||||
relations: {
|
||||
[componentName: string]: RelationOption
|
||||
}
|
||||
/** 组件接受的外部样式类,参见 [外部样式类](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html) */
|
||||
externalClasses?: string[]
|
||||
/** 组件所在页面的生命周期声明对象,参见 [组件生命周期](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html)
|
||||
*
|
||||
* 最低基础库版本: [`2.2.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) */
|
||||
pageLifetimes?: Partial<PageLifetimes>
|
||||
/** 一些选项(文档中介绍相关特性时会涉及具体的选项设置,这里暂不列举) */
|
||||
options: ComponentOptions
|
||||
|
||||
/** 定义段过滤器,用于自定义组件扩展,参见 [自定义组件扩展](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/extend.html)
|
||||
*
|
||||
* 最低基础库版本: [`2.2.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) */
|
||||
definitionFilter?: DefinitionFilter
|
||||
/**
|
||||
* 组件自定义导出,当使用 `behavior: wx://component-export` 时,这个定义段可以用于指定组件被 selectComponent 调用时的返回值,参见 [组件间通信与事件](https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html)
|
||||
* 最低基础库版本: [`2.2.3`](https://developers.weixin.qq.com/miniprogram/dev/framework/compatibility.html) */
|
||||
export: () => IAnyObject
|
||||
}
|
||||
|
||||
interface KeyFrame {
|
||||
/** 关键帧的偏移,范围[0-1] */
|
||||
offset?: number
|
||||
/** 动画缓动函数 */
|
||||
ease?: string
|
||||
/** 基点位置,即 CSS transform-origin */
|
||||
transformOrigin?: string
|
||||
/** 背景颜色,即 CSS background-color */
|
||||
backgroundColor?: string
|
||||
/** 底边位置,即 CSS bottom */
|
||||
bottom?: number | string
|
||||
/** 高度,即 CSS height */
|
||||
height?: number | string
|
||||
/** 左边位置,即 CSS left */
|
||||
left?: number | string
|
||||
/** 宽度,即 CSS width */
|
||||
width?: number | string
|
||||
/** 不透明度,即 CSS opacity */
|
||||
opacity?: number | string
|
||||
/** 右边位置,即 CSS right */
|
||||
right?: number | string
|
||||
/** 顶边位置,即 CSS top */
|
||||
top?: number | string
|
||||
/** 变换矩阵,即 CSS transform matrix */
|
||||
matrix?: number[]
|
||||
/** 三维变换矩阵,即 CSS transform matrix3d */
|
||||
matrix3d?: number[]
|
||||
/** 旋转,即 CSS transform rotate */
|
||||
rotate?: number
|
||||
/** 三维旋转,即 CSS transform rotate3d */
|
||||
rotate3d?: number[]
|
||||
/** X 方向旋转,即 CSS transform rotateX */
|
||||
rotateX?: number
|
||||
/** Y 方向旋转,即 CSS transform rotateY */
|
||||
rotateY?: number
|
||||
/** Z 方向旋转,即 CSS transform rotateZ */
|
||||
rotateZ?: number
|
||||
/** 缩放,即 CSS transform scale */
|
||||
scale?: number[]
|
||||
/** 三维缩放,即 CSS transform scale3d */
|
||||
scale3d?: number[]
|
||||
/** X 方向缩放,即 CSS transform scaleX */
|
||||
scaleX?: number
|
||||
/** Y 方向缩放,即 CSS transform scaleY */
|
||||
scaleY?: number
|
||||
/** Z 方向缩放,即 CSS transform scaleZ */
|
||||
scaleZ?: number
|
||||
/** 倾斜,即 CSS transform skew */
|
||||
skew?: number[]
|
||||
/** X 方向倾斜,即 CSS transform skewX */
|
||||
skewX?: number
|
||||
/** Y 方向倾斜,即 CSS transform skewY */
|
||||
skewY?: number
|
||||
/** 位移,即 CSS transform translate */
|
||||
translate?: Array<number | string>
|
||||
/** 三维位移,即 CSS transform translate3d */
|
||||
translate3d?: Array<number | string>
|
||||
/** X 方向位移,即 CSS transform translateX */
|
||||
translateX?: number | string
|
||||
/** Y 方向位移,即 CSS transform translateY */
|
||||
translateY?: number | string
|
||||
/** Z 方向位移,即 CSS transform translateZ */
|
||||
translateZ?: number | string
|
||||
}
|
||||
interface ClearAnimationOptions {
|
||||
/** 基点位置,即 CSS transform-origin */
|
||||
transformOrigin?: boolean
|
||||
/** 背景颜色,即 CSS background-color */
|
||||
backgroundColor?: boolean
|
||||
/** 底边位置,即 CSS bottom */
|
||||
bottom?: boolean
|
||||
/** 高度,即 CSS height */
|
||||
height?: boolean
|
||||
/** 左边位置,即 CSS left */
|
||||
left?: boolean
|
||||
/** 宽度,即 CSS width */
|
||||
width?: boolean
|
||||
/** 不透明度,即 CSS opacity */
|
||||
opacity?: boolean
|
||||
/** 右边位置,即 CSS right */
|
||||
right?: boolean
|
||||
/** 顶边位置,即 CSS top */
|
||||
top?: boolean
|
||||
/** 变换矩阵,即 CSS transform matrix */
|
||||
matrix?: boolean
|
||||
/** 三维变换矩阵,即 CSS transform matrix3d */
|
||||
matrix3d?: boolean
|
||||
/** 旋转,即 CSS transform rotate */
|
||||
rotate?: boolean
|
||||
/** 三维旋转,即 CSS transform rotate3d */
|
||||
rotate3d?: boolean
|
||||
/** X 方向旋转,即 CSS transform rotateX */
|
||||
rotateX?: boolean
|
||||
/** Y 方向旋转,即 CSS transform rotateY */
|
||||
rotateY?: boolean
|
||||
/** Z 方向旋转,即 CSS transform rotateZ */
|
||||
rotateZ?: boolean
|
||||
/** 缩放,即 CSS transform scale */
|
||||
scale?: boolean
|
||||
/** 三维缩放,即 CSS transform scale3d */
|
||||
scale3d?: boolean
|
||||
/** X 方向缩放,即 CSS transform scaleX */
|
||||
scaleX?: boolean
|
||||
/** Y 方向缩放,即 CSS transform scaleY */
|
||||
scaleY?: boolean
|
||||
/** Z 方向缩放,即 CSS transform scaleZ */
|
||||
scaleZ?: boolean
|
||||
/** 倾斜,即 CSS transform skew */
|
||||
skew?: boolean
|
||||
/** X 方向倾斜,即 CSS transform skewX */
|
||||
skewX?: boolean
|
||||
/** Y 方向倾斜,即 CSS transform skewY */
|
||||
skewY?: boolean
|
||||
/** 位移,即 CSS transform translate */
|
||||
translate?: boolean
|
||||
/** 三维位移,即 CSS transform translate3d */
|
||||
translate3d?: boolean
|
||||
/** X 方向位移,即 CSS transform translateX */
|
||||
translateX?: boolean
|
||||
/** Y 方向位移,即 CSS transform translateY */
|
||||
translateY?: boolean
|
||||
/** Z 方向位移,即 CSS transform translateZ */
|
||||
translateZ?: boolean
|
||||
}
|
||||
interface ScrollTimelineKeyframe {
|
||||
composite?: 'replace' | 'add' | 'accumulate' | 'auto'
|
||||
easing?: string
|
||||
offset?: number | null
|
||||
[property: string]: string | number | null | undefined
|
||||
}
|
||||
interface ScrollTimelineOption {
|
||||
/** 指定滚动元素的选择器(只支持 scroll-view),该元素滚动时会驱动动画的进度 */
|
||||
scrollSource: string
|
||||
/** 指定滚动的方向。有效值为 horizontal 或 vertical */
|
||||
orientation?: string
|
||||
/** 指定开始驱动动画进度的滚动偏移量,单位 px */
|
||||
startScrollOffset: number
|
||||
/** 指定停止驱动动画进度的滚动偏移量,单位 px */
|
||||
endScrollOffset: number
|
||||
/** 起始和结束的滚动范围映射的时间长度,该时间可用于与关键帧动画里的时间 (duration) 相匹配,单位 ms */
|
||||
timeRange: number
|
||||
}
|
||||
|
||||
interface SetUpdatePerformanceListenerOption<WithDataPath> {
|
||||
/** 是否返回变更的 data 字段信息 */
|
||||
withDataPaths?: WithDataPath
|
||||
}
|
||||
interface UpdatePerformanceListener<WithDataPath> {
|
||||
(res: UpdatePerformance<WithDataPath>): void
|
||||
}
|
||||
interface UpdatePerformance<WithDataPath> {
|
||||
/** 此次更新过程的 ID */
|
||||
updateProcessId: number
|
||||
/** 对于子更新,返回它所属的更新过程 ID */
|
||||
parentUpdateProcessId?: number
|
||||
/** 是否是被合并更新,如果是,则 updateProcessId 表示被合并到的更新过程 ID */
|
||||
isMergedUpdate: boolean
|
||||
/** 此次更新的 data 字段信息,只有 withDataPaths 设为 true 时才会返回 */
|
||||
dataPaths: WithDataPath extends true ? string[] : undefined
|
||||
/** 此次更新进入等待队列时的时间戳 */
|
||||
pendingStartTimestamp: number
|
||||
/** 更新运算开始时的时间戳 */
|
||||
updateStartTimestamp: number
|
||||
/** 更新运算结束时的时间戳 */
|
||||
updateEndTimestamp: number
|
||||
}
|
||||
}
|
||||
/** Component构造器可用于定义组件,调用Component构造器时可以指定组件的属性、数据、方法等。
|
||||
*
|
||||
* * 使用 `this.data` 可以获取内部数据和属性值,但不要直接修改它们,应使用 `setData` 修改。
|
||||
* * 生命周期函数无法在组件方法中通过 `this` 访问到。
|
||||
* * 属性名应避免以 data 开头,即不要命名成 `dataXyz` 这样的形式,因为在 WXML 中, `data-xyz=""` 会被作为节点 dataset 来处理,而不是组件属性。
|
||||
* * 在一个组件的定义和使用时,组件的属性名和 data 字段相互间都不能冲突(尽管它们位于不同的定义段中)。
|
||||
* * 从基础库 `2.0.9` 开始,对象类型的属性和 data 字段中可以包含函数类型的子字段,即可以通过对象类型的属性字段来传递函数。低于这一版本的基础库不支持这一特性。
|
||||
* * `bug` : 对于 type 为 Object 或 Array 的属性,如果通过该组件自身的 `this.setData` 来改变属性值的一个子字段,则依旧会触发属性 observer ,且 observer 接收到的 `newVal` 是变化的那个子字段的值, `oldVal` 为空, `changedPath` 包含子字段的字段名相关信息。
|
||||
*/
|
||||
declare let Component: WechatMiniprogram.Component.Constructor
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,272 @@
|
|||
/*! *****************************************************************************
|
||||
Copyright (c) 2023 Tencent, Inc. All rights reserved.
|
||||
|
||||
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.
|
||||
***************************************************************************** */
|
||||
|
||||
declare namespace WechatMiniprogram.Page {
|
||||
type Instance<
|
||||
TData extends DataOption,
|
||||
TCustom extends CustomOption
|
||||
> = OptionalInterface<ILifetime> &
|
||||
InstanceProperties &
|
||||
InstanceMethods<TData> &
|
||||
Data<TData> &
|
||||
TCustom
|
||||
type Options<
|
||||
TData extends DataOption,
|
||||
TCustom extends CustomOption
|
||||
> = (TCustom &
|
||||
Partial<Data<TData>> &
|
||||
Partial<ILifetime> & {
|
||||
options?: Component.ComponentOptions
|
||||
}) &
|
||||
ThisType<Instance<TData, TCustom>>
|
||||
type TrivialInstance = Instance<IAnyObject, IAnyObject>
|
||||
interface Constructor {
|
||||
<TData extends DataOption, TCustom extends CustomOption>(
|
||||
options: Options<TData, TCustom>
|
||||
): void
|
||||
}
|
||||
interface ILifetime {
|
||||
/** 生命周期回调—监听页面加载
|
||||
*
|
||||
* 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。
|
||||
*/
|
||||
onLoad(
|
||||
/** 打开当前页面路径中的参数 */
|
||||
query: Record<string, string | undefined>
|
||||
): void | Promise<void>
|
||||
/** 生命周期回调—监听页面显示
|
||||
*
|
||||
* 页面显示/切入前台时触发。
|
||||
*/
|
||||
onShow(): void | Promise<void>
|
||||
/** 生命周期回调—监听页面初次渲染完成
|
||||
*
|
||||
* 页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。
|
||||
*
|
||||
|
||||
* 注意:对界面内容进行设置的 API 如`wx.setNavigationBarTitle`,请在`onReady`之后进行。
|
||||
*/
|
||||
onReady(): void | Promise<void>
|
||||
/** 生命周期回调—监听页面隐藏
|
||||
*
|
||||
* 页面隐藏/切入后台时触发。 如 `navigateTo` 或底部 `tab` 切换到其他页面,小程序切入后台等。
|
||||
*/
|
||||
onHide(): void | Promise<void>
|
||||
/** 生命周期回调—监听页面卸载
|
||||
*
|
||||
* 页面卸载时触发。如`redirectTo`或`navigateBack`到其他页面时。
|
||||
*/
|
||||
onUnload(): void | Promise<void>
|
||||
/** 监听用户下拉动作
|
||||
*
|
||||
* 监听用户下拉刷新事件。
|
||||
* - 需要在`app.json`的`window`选项中或页面配置中开启`enablePullDownRefresh`。
|
||||
* - 可以通过`wx.startPullDownRefresh`触发下拉刷新,调用后触发下拉刷新动画,效果与用户手动下拉刷新一致。
|
||||
* - 当处理完数据刷新后,`wx.stopPullDownRefresh`可以停止当前页面的下拉刷新。
|
||||
*/
|
||||
onPullDownRefresh(): void | Promise<void>
|
||||
/** 页面上拉触底事件的处理函数
|
||||
*
|
||||
* 监听用户上拉触底事件。
|
||||
* - 可以在`app.json`的`window`选项中或页面配置中设置触发距离`onReachBottomDistance`。
|
||||
* - 在触发距离内滑动期间,本事件只会被触发一次。
|
||||
*/
|
||||
onReachBottom(): void | Promise<void>
|
||||
/** 用户点击右上角转发
|
||||
*
|
||||
* 监听用户点击页面内转发按钮(`<button>` 组件 `open-type="share"`)或右上角菜单“转发”按钮的行为,并自定义转发内容。
|
||||
*
|
||||
* **注意:只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮**
|
||||
*
|
||||
* 此事件需要 return 一个 Object,用于自定义转发内容
|
||||
*/
|
||||
onShareAppMessage(
|
||||
/** 分享发起来源参数 */
|
||||
options: IShareAppMessageOption
|
||||
):
|
||||
| ICustomShareContent
|
||||
| IAsyncCustomShareContent
|
||||
| Promise<ICustomShareContent>
|
||||
| void
|
||||
| Promise<void>
|
||||
/**
|
||||
* 监听右上角菜单“分享到朋友圈”按钮的行为,并自定义分享内容
|
||||
*
|
||||
* 本接口为 Beta 版本,暂只在 Android 平台支持,详见 [分享到朋友圈 (Beta)](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html)
|
||||
*
|
||||
* 基础库 2.11.3 开始支持,低版本需做兼容处理。
|
||||
*/
|
||||
onShareTimeline(): ICustomTimelineContent | void
|
||||
|
||||
/** 页面滚动触发事件的处理函数
|
||||
*
|
||||
* 监听用户滑动页面事件。
|
||||
*/
|
||||
onPageScroll(
|
||||
/** 页面滚动参数 */
|
||||
options: IPageScrollOption
|
||||
): void | Promise<void>
|
||||
|
||||
/** 当前是 tab 页时,点击 tab 时触发,最低基础库: `1.9.0` */
|
||||
onTabItemTap(
|
||||
/** tab 点击参数 */
|
||||
options: ITabItemTapOption
|
||||
): void | Promise<void>
|
||||
|
||||
/** 窗口尺寸改变时触发,最低基础库:`2.4.0` */
|
||||
onResize(
|
||||
/** 窗口尺寸参数 */
|
||||
options: IResizeOption
|
||||
): void | Promise<void>
|
||||
|
||||
/**
|
||||
* 监听用户点击右上角菜单“收藏”按钮的行为,并自定义收藏内容。
|
||||
* 基础库 2.10.3,安卓 7.0.15 版本起支持,iOS 暂不支持
|
||||
*/
|
||||
onAddToFavorites(options: IAddToFavoritesOption): IAddToFavoritesContent
|
||||
}
|
||||
interface InstanceProperties {
|
||||
/** 页面的文件路径 */
|
||||
is: string
|
||||
|
||||
/** 到当前页面的路径 */
|
||||
route: string
|
||||
|
||||
/** 打开当前页面路径中的参数 */
|
||||
options: Record<string, string | undefined>
|
||||
}
|
||||
|
||||
type DataOption = Record<string, any>
|
||||
type CustomOption = Record<string, any>
|
||||
|
||||
type InstanceMethods<D extends DataOption> = Component.InstanceMethods<D>
|
||||
|
||||
interface Data<D extends DataOption> {
|
||||
/** 页面的初始数据
|
||||
*
|
||||
* `data` 是页面第一次渲染使用的**初始数据**。
|
||||
*
|
||||
* 页面加载时,`data` 将会以`JSON`字符串的形式由逻辑层传至渲染层,因此`data`中的数据必须是可以转成`JSON`的类型:字符串,数字,布尔值,对象,数组。
|
||||
*
|
||||
* 渲染层可以通过 `WXML` 对数据进行绑定。
|
||||
*/
|
||||
data: D
|
||||
}
|
||||
|
||||
interface ICustomShareContent {
|
||||
/** 转发标题。默认值:当前小程序名称 */
|
||||
title?: string
|
||||
/** 转发路径,必须是以 / 开头的完整路径。默认值:当前页面 path */
|
||||
path?: string
|
||||
/** 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径。支持PNG及JPG。显示图片长宽比是 5:4,最低基础库: `1.5.0`。默认值:使用默认截图 */
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
interface IAsyncCustomShareContent extends ICustomShareContent {
|
||||
promise: Promise<ICustomShareContent>
|
||||
}
|
||||
|
||||
interface ICustomTimelineContent {
|
||||
/** 自定义标题,即朋友圈列表页上显示的标题。默认值:当前小程序名称 */
|
||||
title?: string
|
||||
/** 自定义页面路径中携带的参数,如 `path?a=1&b=2` 的 “?” 后面部分 默认值:当前页面路径携带的参数 */
|
||||
query?: string
|
||||
/** 自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径。支持 PNG 及 JPG。显示图片长宽比是 1:1。默认值:默认使用小程序 Logo*/
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
interface IPageScrollOption {
|
||||
/** 页面在垂直方向已滚动的距离(单位px) */
|
||||
scrollTop: number
|
||||
}
|
||||
|
||||
interface IShareAppMessageOption {
|
||||
/** 转发事件来源。
|
||||
*
|
||||
* 可选值:
|
||||
* - `button`:页面内转发按钮;
|
||||
* - `menu`:右上角转发菜单。
|
||||
*
|
||||
* 最低基础库: `1.2.4`
|
||||
*/
|
||||
from: 'button' | 'menu'
|
||||
/** 如果 `from` 值是 `button`,则 `target` 是触发这次转发事件的 `button`,否则为 `undefined`
|
||||
*
|
||||
* 最低基础库: `1.2.4` */
|
||||
target: any
|
||||
/** 页面中包含`<web-view>`组件时,返回当前`<web-view>`的url
|
||||
*
|
||||
* 最低基础库: `1.6.4`
|
||||
*/
|
||||
webViewUrl?: string
|
||||
}
|
||||
|
||||
interface ITabItemTapOption {
|
||||
/** 被点击tabItem的序号,从0开始,最低基础库: `1.9.0` */
|
||||
index: string
|
||||
/** 被点击tabItem的页面路径,最低基础库: `1.9.0` */
|
||||
pagePath: string
|
||||
/** 被点击tabItem的按钮文字,最低基础库: `1.9.0` */
|
||||
text: string
|
||||
}
|
||||
|
||||
interface IResizeOption {
|
||||
size: {
|
||||
/** 变化后的窗口宽度,单位 px */
|
||||
windowWidth: number
|
||||
/** 变化后的窗口高度,单位 px */
|
||||
windowHeight: number
|
||||
}
|
||||
}
|
||||
|
||||
interface IAddToFavoritesOption {
|
||||
/** 页面中包含web-view组件时,返回当前web-view的url */
|
||||
webviewUrl?: string
|
||||
}
|
||||
|
||||
interface IAddToFavoritesContent {
|
||||
/** 自定义标题,默认值:页面标题或账号名称 */
|
||||
title?: string
|
||||
/** 自定义图片,显示图片长宽比为 1:1,默认值:页面截图 */
|
||||
imageUrl?: string
|
||||
/** 自定义query字段,默认值:当前页面的query */
|
||||
query?: string
|
||||
}
|
||||
|
||||
interface GetCurrentPages {
|
||||
(): Array<Instance<IAnyObject, IAnyObject>>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册小程序中的一个页面。接受一个 `Object` 类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。
|
||||
*/
|
||||
declare let Page: WechatMiniprogram.Page.Constructor
|
||||
/**
|
||||
* 获取当前页面栈。数组中第一个元素为首页,最后一个元素为当前页面。
|
||||
|
||||
* __注意:__
|
||||
|
||||
* - __不要尝试修改页面栈,会导致路由以及页面状态错误。__
|
||||
* - 不要在 `App.onLaunch` 的时候调用 `getCurrentPages()`,此时 `page` 还没有生成。
|
||||
*/
|
||||
declare let getCurrentPages: WechatMiniprogram.Page.GetCurrentPages
|
|
@ -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="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,32 @@
|
|||
import App from './App'
|
||||
|
||||
import uView from "uview-ui";
|
||||
Vue.use(uView);
|
||||
|
||||
// #ifndef VUE3
|
||||
import Vue from 'vue'
|
||||
import './uni.promisify.adaptor'
|
||||
Vue.config.productionTip = false
|
||||
App.mpType = 'app'
|
||||
const app = new Vue({
|
||||
...App
|
||||
})
|
||||
app.$mount()
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
import { createSSRApp } from 'vue'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
//接口 this.api
|
||||
import api from '@/api/driverapi.js'
|
||||
Vue.prototype.api = api;
|
||||
//
|
||||
import {myRequest} from "./api/nvuerequest"
|
||||
Vue.prototype.$http=myRequest
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"name" : "greenhouse-wisdom-applet",
|
||||
"appid" : "__UNI__1066059",
|
||||
"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" : "wx05b45a2699f02a2b",
|
||||
"setting" : {
|
||||
"urlCheck" : false
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
},
|
||||
"vueVersion" : "2"
|
||||
}
|
|
@ -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,106 @@
|
|||
<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</h3>
|
||||
<h3 align="center">多平台快速开发的UI框架</h3>
|
||||
|
||||
|
||||
## 说明
|
||||
|
||||
uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
|
||||
|
||||
## 特性
|
||||
|
||||
- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序
|
||||
- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
|
||||
- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨
|
||||
- 众多的常用页面和布局,让您专注逻辑,事半功倍
|
||||
- 详尽的文档支持,现代化的演示效果
|
||||
- 按需引入,精简打包体积
|
||||
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# npm方式安装
|
||||
npm i uview-ui
|
||||
```
|
||||
|
||||
## 快速上手
|
||||
|
||||
1. `main.js`引入uView库
|
||||
```js
|
||||
// main.js
|
||||
import uView from 'uview-ui';
|
||||
Vue.use(uView);
|
||||
```
|
||||
|
||||
2. `App.vue`引入基础样式(注意style标签需声明scss属性支持)
|
||||
```css
|
||||
/* App.vue */
|
||||
<style lang="scss">
|
||||
@import "uview-ui/index.scss";
|
||||
</style>
|
||||
```
|
||||
|
||||
3. `uni.scss`引入全局scss变量文件
|
||||
```css
|
||||
/* uni.scss */
|
||||
@import "uview-ui/theme.scss";
|
||||
```
|
||||
|
||||
4. `pages.json`配置easycom规则(按需引入)
|
||||
|
||||
```js
|
||||
// pages.json
|
||||
{
|
||||
"easycom": {
|
||||
// npm安装的方式不需要前面的"@/",下载安装的方式需要"@/"
|
||||
// npm安装方式
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
|
||||
// 下载安装方式
|
||||
// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
|
||||
},
|
||||
// 此为本身已有的内容
|
||||
"pages": [
|
||||
// ......
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
|
||||
|
||||
## 使用方法
|
||||
配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
|
||||
|
||||
```html
|
||||
<template>
|
||||
<u-button>按钮</u-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
|
||||
|
||||
## 链接
|
||||
|
||||
- [官方文档](https://uviewui.com/)
|
||||
- [更新日志](https://uviewui.com/components/changelog.html)
|
||||
- [升级指南](https://uviewui.com/components/changelog.html)
|
||||
- [关于我们](https://uviewui.com/cooperation/about.html)
|
||||
|
||||
## 预览
|
||||
|
||||
您可以通过**微信**扫码,查看最佳的演示效果。
|
||||
<br>
|
||||
<br>
|
||||
<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
|
||||
|
||||
<!-- ## 捐赠uView的研发
|
||||
|
||||
uView文档和源码全部开源免费,如果您认为uView帮到了您的开发工作,您可以捐赠uView的研发工作,捐赠无门槛,哪怕是一杯可乐也好(相信这比打赏主播更有意义)。
|
||||
|
||||
<img src="https://uviewui.com/common/wechat.png" width="220" >
|
||||
<img style="margin-left: 100px;" src="https://uviewui.com/common/alipay.png" width="220" >
|
||||
-->
|
||||
## 版权信息
|
||||
uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。
|
|
@ -0,0 +1,190 @@
|
|||
<template>
|
||||
<u-popup mode="bottom" :border-radius="borderRadius" :popup="false" v-model="value" :maskCloseAble="maskCloseAble"
|
||||
length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :z-index="uZIndex">
|
||||
<view class="u-tips u-border-bottom" v-if="tips.text" :style="[tipsStyle]">
|
||||
{{tips.text}}
|
||||
</view>
|
||||
<block v-for="(item, index) in list" :key="index">
|
||||
<view
|
||||
@touchmove.stop.prevent
|
||||
@tap="itemClick(index)"
|
||||
:style="[itemStyle(index)]"
|
||||
class="u-action-sheet-item u-line-1"
|
||||
:class="[index < list.length - 1 ? 'u-border-bottom' : '']"
|
||||
:hover-stay-time="150"
|
||||
>
|
||||
<text>{{item.text}}</text>
|
||||
<text class="u-action-sheet-item__subtext u-line-1" v-if="item.subText">{{item.subText}}</text>
|
||||
</view>
|
||||
</block>
|
||||
<view class="u-gab" v-if="cancelBtn">
|
||||
</view>
|
||||
<view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class"
|
||||
:hover-stay-time="150" v-if="cancelBtn" @tap="close">{{cancelText}}</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* actionSheet 操作菜单
|
||||
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
|
||||
* @tutorial https://www.uviewui.com/components/actionSheet.html
|
||||
* @property {Array<Object>} list 按钮的文字数组,见官方文档示例
|
||||
* @property {Object} tips 顶部的提示文字,见官方文档示例
|
||||
* @property {String} cancel-text 取消按钮的提示文字
|
||||
* @property {Boolean} cancel-btn 是否显示底部的取消按钮(默认true)
|
||||
* @property {Number String} border-radius 弹出部分顶部左右的圆角值,单位rpx(默认0)
|
||||
* @property {Boolean} mask-close-able 点击遮罩是否可以关闭(默认true)
|
||||
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
|
||||
* @property {Number String} z-index z-index值(默认1075)
|
||||
* @property {String} cancel-text 取消按钮的提示文字
|
||||
* @event {Function} click 点击ActionSheet列表项时触发
|
||||
* @event {Function} close 点击取消按钮时触发
|
||||
* @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet>
|
||||
*/
|
||||
export default {
|
||||
name: "u-action-sheet",
|
||||
props: {
|
||||
// 点击遮罩是否可以关闭actionsheet
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx
|
||||
list: {
|
||||
type: Array,
|
||||
default () {
|
||||
// 如下
|
||||
// return [{
|
||||
// text: '确定',
|
||||
// color: '',
|
||||
// fontSize: ''
|
||||
// }]
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 顶部的提示文字
|
||||
tips: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
text: '',
|
||||
color: '',
|
||||
fontSize: '26'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 底部的取消按钮
|
||||
cancelBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 通过双向绑定控制组件的弹出与收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹出的顶部圆角值
|
||||
borderRadius: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 弹出的z-index值
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 取消按钮的文字提示
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 顶部提示的样式
|
||||
tipsStyle() {
|
||||
let style = {};
|
||||
if (this.tips.color) style.color = this.tips.color;
|
||||
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx';
|
||||
return style;
|
||||
},
|
||||
// 操作项目的样式
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {};
|
||||
if (this.list[index].color) style.color = this.list[index].color;
|
||||
if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx';
|
||||
// 选项被禁用的样式
|
||||
if (this.list[index].disabled) style.color = '#c0c4cc';
|
||||
return style;
|
||||
}
|
||||
},
|
||||
uZIndex() {
|
||||
// 如果用户有传递z-index值,优先使用
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击取消按钮
|
||||
close() {
|
||||
// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
|
||||
// 这是一个vue发送事件的特殊用法
|
||||
this.popupClose();
|
||||
this.$emit('close');
|
||||
},
|
||||
// 弹窗关闭
|
||||
popupClose() {
|
||||
this.$emit('input', false);
|
||||
},
|
||||
// 点击某一个item
|
||||
itemClick(index) {
|
||||
// disabled的项禁止点击
|
||||
if(this.list[index].disabled) return;
|
||||
this.$emit('click', index);
|
||||
this.$emit('input', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-tips {
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
padding: 34rpx 0;
|
||||
line-height: 1;
|
||||
color: $u-tips-color;
|
||||
}
|
||||
|
||||
.u-action-sheet-item {
|
||||
@include vue-flex;;
|
||||
line-height: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 32rpx;
|
||||
padding: 34rpx 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.u-action-sheet-item__subtext {
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.u-gab {
|
||||
height: 12rpx;
|
||||
background-color: rgb(234, 234, 236);
|
||||
}
|
||||
|
||||
.u-actionsheet-cancel {
|
||||
color: $u-main-color;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,256 @@
|
|||
<template>
|
||||
<view class="u-alert-tips" v-if="show" :class="[
|
||||
!show ? 'u-close-alert-tips': '',
|
||||
type ? 'u-alert-tips--bg--' + type + '-light' : '',
|
||||
type ? 'u-alert-tips--border--' + type + '-disabled' : '',
|
||||
]" :style="{
|
||||
backgroundColor: bgColor,
|
||||
borderColor: borderColor
|
||||
}">
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon v-if="showIcon" :name="uIcon" :size="description ? 40 : 32" class="u-icon" :color="uIconType" :custom-style="iconStyle"></u-icon>
|
||||
</view>
|
||||
<view class="u-alert-content" @tap.stop="click">
|
||||
<view class="u-alert-title" :style="[uTitleStyle]">
|
||||
{{title}}
|
||||
</view>
|
||||
<view v-if="description" class="u-alert-desc" :style="[descStyle]">
|
||||
{{description}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon @click="close" v-if="closeAble && !closeText" hoverClass="u-type-error-hover-color" name="close" color="#c0c4cc"
|
||||
:size="22" class="u-close-icon" :style="{
|
||||
top: description ? '18rpx' : '24rpx'
|
||||
}"></u-icon>
|
||||
</view>
|
||||
<text v-if="closeAble && closeText" class="u-close-text" :style="{
|
||||
top: description ? '18rpx' : '24rpx'
|
||||
}">{{closeText}}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* alertTips 警告提示
|
||||
* @description 警告提示,展现需要关注的信息
|
||||
* @tutorial https://uviewui.com/components/alertTips.html
|
||||
* @property {String} title 显示的标题文字
|
||||
* @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
|
||||
* @property {String} type 关闭按钮(默认为叉号icon图标)
|
||||
* @property {String} icon 图标名称
|
||||
* @property {Object} icon-style 图标的样式,对象形式
|
||||
* @property {Object} title-style 标题的样式,对象形式
|
||||
* @property {Object} desc-style 描述的样式,对象形式
|
||||
* @property {String} close-able 用文字替代关闭图标,close-able为true时有效
|
||||
* @property {Boolean} show-icon 是否显示左边的辅助图标
|
||||
* @property {Boolean} show 显示或隐藏组件
|
||||
* @event {Function} click 点击组件时触发
|
||||
* @event {Function} close 点击关闭按钮时触发
|
||||
*/
|
||||
export default {
|
||||
name: 'u-alert-tips',
|
||||
props: {
|
||||
// 显示文字
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 主题,success/warning/info/error
|
||||
type: {
|
||||
type: String,
|
||||
default: 'warning'
|
||||
},
|
||||
// 辅助性文字
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否可关闭
|
||||
closeAble: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 关闭按钮自定义文本
|
||||
closeText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示图标
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 文字颜色,如果定义了color值,icon会失效
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边显示的icon
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// icon的样式
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 标题的样式
|
||||
titleStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 描述文字的样式
|
||||
descStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uTitleStyle() {
|
||||
let style = {};
|
||||
// 如果有描述文字的话,标题进行加粗
|
||||
style.fontWeight = this.description ? 500 : 'normal';
|
||||
// 将用户传入样式对象和style合并,传入的优先级比style高,同属性会被覆盖
|
||||
return this.$u.deepMerge(style, this.titleStyle);
|
||||
},
|
||||
uIcon() {
|
||||
// 如果有设置icon名称就使用,否则根据type主题,推定一个默认的图标
|
||||
return this.icon ? this.icon : this.$u.type2icon(this.type);
|
||||
},
|
||||
uIconType() {
|
||||
// 如果有设置图标的样式,优先使用,没有的话,则用type的样式
|
||||
return Object.keys(this.iconStyle).length ? '' : this.type;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击内容
|
||||
click() {
|
||||
this.$emit('click');
|
||||
},
|
||||
// 点击关闭按钮
|
||||
close() {
|
||||
this.$emit('close');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-alert-tips {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 30rpx;
|
||||
border-radius: 8rpx;
|
||||
position: relative;
|
||||
transition: all 0.3s linear;
|
||||
border: 1px solid #fff;
|
||||
|
||||
&--bg--primary-light {
|
||||
background-color: $u-type-primary-light;
|
||||
}
|
||||
|
||||
&--bg--info-light {
|
||||
background-color: $u-type-info-light;
|
||||
}
|
||||
|
||||
&--bg--success-light {
|
||||
background-color: $u-type-success-light;
|
||||
}
|
||||
|
||||
&--bg--warning-light {
|
||||
background-color: $u-type-warning-light;
|
||||
}
|
||||
|
||||
&--bg--error-light {
|
||||
background-color: $u-type-error-light;
|
||||
}
|
||||
|
||||
&--border--primary-disabled {
|
||||
border-color: $u-type-primary-disabled;
|
||||
}
|
||||
|
||||
&--border--success-disabled {
|
||||
border-color: $u-type-success-disabled;
|
||||
}
|
||||
|
||||
&--border--error-disabled {
|
||||
border-color: $u-type-error-disabled;
|
||||
}
|
||||
|
||||
&--border--warning-disabled {
|
||||
border-color: $u-type-warning-disabled;
|
||||
}
|
||||
|
||||
&--border--info-disabled {
|
||||
border-color: $u-type-info-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
.u-close-alert-tips {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.u-icon {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.u-alert-title {
|
||||
font-size: 28rpx;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
.u-alert-desc {
|
||||
font-size: 26rpx;
|
||||
text-align: left;
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
.u-close-icon {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
}
|
||||
|
||||
.u-close-hover {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.u-close-text {
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
290
node_modules/uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue
generated
vendored
100644
290
node_modules/uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue
generated
vendored
100644
|
@ -0,0 +1,290 @@
|
|||
<template>
|
||||
<view class="content">
|
||||
<view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }">
|
||||
<canvas
|
||||
class="cropper"
|
||||
:disable-scroll="true"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
:style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }"
|
||||
canvas-id="cropper"
|
||||
id="cropper"
|
||||
></canvas>
|
||||
<canvas
|
||||
class="cropper"
|
||||
:disable-scroll="true"
|
||||
:style="{
|
||||
position: 'fixed',
|
||||
top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`,
|
||||
left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`,
|
||||
width: `${cropperOpt.width * cropperOpt.pixelRatio}px`,
|
||||
height: `${cropperOpt.height * cropperOpt.pixelRatio}`
|
||||
}"
|
||||
canvas-id="targetId"
|
||||
id="targetId"
|
||||
></canvas>
|
||||
</view>
|
||||
<view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }">
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="upload" @tap="uploadTap">选择图片</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 -->
|
||||
<view class="upload" @tap="uploadTap">重新选择</view>
|
||||
<!-- #endif -->
|
||||
<view class="getCropperImage" @tap="getCropperImage(false)">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WeCropper from './weCropper.js';
|
||||
export default {
|
||||
props: {
|
||||
// 裁剪矩形框的样式,其中可包含的属性为lineWidth-边框宽度(单位rpx),color: 边框颜色,
|
||||
// mask-遮罩颜色,一般设置为一个rgba的透明度,如"rgba(0, 0, 0, 0.35)"
|
||||
boundStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
lineWidth: 4,
|
||||
borderColor: 'rgb(245, 245, 245)',
|
||||
mask: 'rgba(0, 0, 0, 0.35)'
|
||||
};
|
||||
}
|
||||
}
|
||||
// // 裁剪框宽度,单位rpx
|
||||
// rectWidth: {
|
||||
// type: [String, Number],
|
||||
// default: 400
|
||||
// },
|
||||
// // 裁剪框高度,单位rpx
|
||||
// rectHeight: {
|
||||
// type: [String, Number],
|
||||
// default: 400
|
||||
// },
|
||||
// // 输出图片宽度,单位rpx
|
||||
// destWidth: {
|
||||
// type: [String, Number],
|
||||
// default: 400
|
||||
// },
|
||||
// // 输出图片高度,单位rpx
|
||||
// destHeight: {
|
||||
// type: [String, Number],
|
||||
// default: 400
|
||||
// },
|
||||
// // 输出的图片类型,如果发现裁剪的图片很大,可能是因为设置为了"png",改成"jpg"即可
|
||||
// fileType: {
|
||||
// type: String,
|
||||
// default: 'jpg',
|
||||
// },
|
||||
// // 生成的图片质量
|
||||
// // H5上无效,目前不考虑使用此参数
|
||||
// quality: {
|
||||
// type: [Number, String],
|
||||
// default: 1
|
||||
// }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 底部导航的高度
|
||||
bottomNavHeight: 50,
|
||||
originWidth: 200,
|
||||
width: 0,
|
||||
height: 0,
|
||||
cropperOpt: {
|
||||
id: 'cropper',
|
||||
targetId: 'targetCropper',
|
||||
pixelRatio: 1,
|
||||
width: 0,
|
||||
height: 0,
|
||||
scale: 2.5,
|
||||
zoom: 8,
|
||||
cut: {
|
||||
x: (this.width - this.originWidth) / 2,
|
||||
y: (this.height - this.originWidth) / 2,
|
||||
width: this.originWidth,
|
||||
height: this.originWidth
|
||||
},
|
||||
boundStyle: {
|
||||
lineWidth: uni.upx2px(this.boundStyle.lineWidth),
|
||||
mask: this.boundStyle.mask,
|
||||
color: this.boundStyle.borderColor
|
||||
}
|
||||
},
|
||||
// 裁剪框和输出图片的尺寸,高度默认等于宽度
|
||||
// 输出图片宽度,单位px
|
||||
destWidth: 200,
|
||||
// 裁剪框宽度,单位px
|
||||
rectWidth: 200,
|
||||
// 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可
|
||||
fileType: 'jpg',
|
||||
src: '', // 选择的图片路径,用于在点击确定时,判断是否选择了图片
|
||||
};
|
||||
},
|
||||
onLoad(option) {
|
||||
let rectInfo = uni.getSystemInfoSync();
|
||||
this.width = rectInfo.windowWidth;
|
||||
this.height = rectInfo.windowHeight - this.bottomNavHeight;
|
||||
this.cropperOpt.width = this.width;
|
||||
this.cropperOpt.height = this.height;
|
||||
this.cropperOpt.pixelRatio = rectInfo.pixelRatio;
|
||||
|
||||
if (option.destWidth) this.destWidth = option.destWidth;
|
||||
if (option.rectWidth) {
|
||||
let rectWidth = Number(option.rectWidth);
|
||||
this.cropperOpt.cut = {
|
||||
x: (this.width - rectWidth) / 2,
|
||||
y: (this.height - rectWidth) / 2,
|
||||
width: rectWidth,
|
||||
height: rectWidth
|
||||
};
|
||||
}
|
||||
this.rectWidth = option.rectWidth;
|
||||
if (option.fileType) this.fileType = option.fileType;
|
||||
// 初始化
|
||||
this.cropper = new WeCropper(this.cropperOpt)
|
||||
.on('ready', ctx => {
|
||||
// wecropper is ready for work!
|
||||
})
|
||||
.on('beforeImageLoad', ctx => {
|
||||
// before picture loaded, i can do something
|
||||
})
|
||||
.on('imageLoad', ctx => {
|
||||
// picture loaded
|
||||
})
|
||||
.on('beforeDraw', (ctx, instance) => {
|
||||
// before canvas draw,i can do something
|
||||
});
|
||||
// 设置导航栏样式,以免用户在page.json中没有设置为黑色背景
|
||||
uni.setNavigationBarColor({
|
||||
frontColor: '#ffffff',
|
||||
backgroundColor: '#000000'
|
||||
});
|
||||
uni.chooseImage({
|
||||
count: 1, // 默认9
|
||||
sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
|
||||
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
|
||||
success: res => {
|
||||
this.src = res.tempFilePaths[0];
|
||||
// 获取裁剪图片资源后,给data添加src属性及其值
|
||||
this.cropper.pushOrign(this.src);
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
touchStart(e) {
|
||||
this.cropper.touchStart(e);
|
||||
},
|
||||
touchMove(e) {
|
||||
this.cropper.touchMove(e);
|
||||
},
|
||||
touchEnd(e) {
|
||||
this.cropper.touchEnd(e);
|
||||
},
|
||||
getCropperImage(isPre = false) {
|
||||
if(!this.src) return this.$u.toast('请先选择图片再裁剪');
|
||||
|
||||
let cropper_opt = {
|
||||
destHeight: Number(this.destWidth), // uni.canvasToTempFilePath要求这些参数为数值
|
||||
destWidth: Number(this.destWidth),
|
||||
fileType: this.fileType
|
||||
};
|
||||
this.cropper.getCropperImage(cropper_opt, (path, err) => {
|
||||
if (err) {
|
||||
uni.showModal({
|
||||
title: '温馨提示',
|
||||
content: err.message
|
||||
});
|
||||
} else {
|
||||
if (isPre) {
|
||||
uni.previewImage({
|
||||
current: '', // 当前显示图片的 http 链接
|
||||
urls: [path] // 需要预览的图片 http 链接列表
|
||||
});
|
||||
} else {
|
||||
uni.$emit('uAvatarCropper', path);
|
||||
this.$u.route({
|
||||
type: 'back'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
uploadTap() {
|
||||
const self = this;
|
||||
uni.chooseImage({
|
||||
count: 1, // 默认9
|
||||
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
|
||||
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
|
||||
success: (res) => {
|
||||
self.src = res.tempFilePaths[0];
|
||||
// 获取裁剪图片资源后,给data添加src属性及其值
|
||||
|
||||
self.cropper.pushOrign(this.src);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../libs/css/style.components.scss';
|
||||
|
||||
.content {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.cropper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.cropper-buttons {
|
||||
background-color: #000000;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.cropper-wrapper {
|
||||
position: relative;
|
||||
@include vue-flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.cropper-buttons {
|
||||
width: 100vw;
|
||||
@include vue-flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.cropper-buttons .upload,
|
||||
.cropper-buttons .getCropperImage {
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cropper-buttons .upload {
|
||||
text-align: left;
|
||||
padding-left: 50rpx;
|
||||
}
|
||||
|
||||
.cropper-buttons .getCropperImage {
|
||||
text-align: right;
|
||||
padding-right: 50rpx;
|
||||
}
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,244 @@
|
|||
<template>
|
||||
<view class="u-avatar" :style="[wrapStyle]" @tap="click">
|
||||
<image
|
||||
@error="loadError"
|
||||
:style="[imgStyle]"
|
||||
class="u-avatar__img"
|
||||
v-if="!uText && avatar"
|
||||
:src="avatar"
|
||||
:mode="imgMode"
|
||||
></image>
|
||||
<text class="u-line-1" v-else-if="uText" :style="{
|
||||
fontSize: '38rpx'
|
||||
}">{{uText}}</text>
|
||||
<slot v-else></slot>
|
||||
<view class="u-avatar__sex" v-if="showSex" :class="['u-avatar__sex--' + sexIcon]" :style="[uSexStyle]">
|
||||
<u-icon :name="sexIcon" size="20"></u-icon>
|
||||
</view>
|
||||
<view class="u-avatar__level" v-if="showLevel" :style="[uLevelStyle]">
|
||||
<u-icon :name="levelIcon" size="20"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let 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://www.uviewui.com/components/avatar.html
|
||||
* @property {String} bg-color 背景颜色,一般显示文字时用(默认#ffffff)
|
||||
* @property {String} src 头像路径,如加载失败,将会显示默认头像
|
||||
* @property {String Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值,单位rpx(默认default)
|
||||
* @property {String} mode 显示类型,见上方说明(默认circle)
|
||||
* @property {String} sex-icon 性别图标,man-男,woman-女(默认man)
|
||||
* @property {String} level-icon 等级图标(默认level)
|
||||
* @property {String} sex-bg-color 性别图标背景颜色
|
||||
* @property {String} level-bg-color 等级图标背景颜色
|
||||
* @property {String} show-sex 是否显示性别图标(默认false)
|
||||
* @property {String} show-level 是否显示等级图标(默认false)
|
||||
* @property {String} img-mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值(默认aspectFill)
|
||||
* @property {String} index 用户传递的标识符值,如果是列表循环,可穿v-for的index值
|
||||
* @event {Function} click 头像被点击
|
||||
* @example <u-avatar :src="src"></u-avatar>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-avatar',
|
||||
props: {
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
// 头像路径
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 尺寸,large-大,default-中等,mini-小,如果为数值,则单位为rpx
|
||||
// 宽度等于高度
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 'default'
|
||||
},
|
||||
// 头像模型,square-带圆角方形,circle-圆形
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 文字内容
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图片的裁剪模型
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 标识符
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 右上角性别角标,man-男,woman-女
|
||||
sexIcon: {
|
||||
type: String,
|
||||
default: 'man'
|
||||
},
|
||||
// 右下角的等级图标
|
||||
levelIcon: {
|
||||
type: String,
|
||||
default: 'level'
|
||||
},
|
||||
// 右下角等级图标背景颜色
|
||||
levelBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 右上角性别图标的背景颜色
|
||||
sexBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示性别图标
|
||||
showSex: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示等级图标
|
||||
showLevel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
// 头像的地址,因为如果加载错误,需要赋值为默认图片,props值无法修改,所以需要一个中间值
|
||||
avatar: this.src ? this.src : base64Avatar,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
src(n) {
|
||||
// 用户可能会在头像加载失败时,再次修改头像值,所以需要重新赋值
|
||||
if(!n) {
|
||||
// 如果传入null或者'',或者undefined,显示默认头像
|
||||
this.avatar = base64Avatar;
|
||||
this.error = true;
|
||||
} else {
|
||||
this.avatar = n;
|
||||
this.error = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wrapStyle() {
|
||||
let style = {};
|
||||
style.height = this.size == 'large' ? '120rpx' : this.size == 'default' ?
|
||||
'90rpx' : this.size == 'mini' ? '70rpx' : this.size + 'rpx';
|
||||
style.width = style.height;
|
||||
style.flex = `0 0 ${style.height}`;
|
||||
style.backgroundColor = this.bgColor;
|
||||
style.borderRadius = this.mode == 'circle' ? '500px' : '5px';
|
||||
if(this.text) style.padding = `0 6rpx`;
|
||||
return style;
|
||||
},
|
||||
imgStyle() {
|
||||
let style = {};
|
||||
style.borderRadius = this.mode == 'circle' ? '500px' : '5px';
|
||||
return style;
|
||||
},
|
||||
// 取字符串的第一个字符
|
||||
uText() {
|
||||
return String(this.text)[0];
|
||||
},
|
||||
// 性别图标的自定义样式
|
||||
uSexStyle() {
|
||||
let style = {};
|
||||
if(this.sexBgColor) style.backgroundColor = this.sexBgColor;
|
||||
return style;
|
||||
},
|
||||
// 等级图标的自定义样式
|
||||
uLevelStyle() {
|
||||
let style = {};
|
||||
if(this.levelBgColor) style.backgroundColor = this.levelBgColor;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 图片加载错误时,显示默认头像
|
||||
loadError() {
|
||||
this.error = true;
|
||||
this.avatar = base64Avatar;
|
||||
},
|
||||
click() {
|
||||
this.$emit('click', this.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-avatar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
color: $u-content-color;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
|
||||
&__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__sex {
|
||||
position: absolute;
|
||||
width: 32rpx;
|
||||
color: #ffffff;
|
||||
height: 32rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 100rpx;
|
||||
top: 5%;
|
||||
z-index: 1;
|
||||
right: -7%;
|
||||
border: 1px #ffffff solid;
|
||||
|
||||
&--man {
|
||||
background-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--woman {
|
||||
background-color: $u-type-error;
|
||||
}
|
||||
|
||||
&--none {
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
|
||||
&__level {
|
||||
position: absolute;
|
||||
width: 32rpx;
|
||||
color: #ffffff;
|
||||
height: 32rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 100rpx;
|
||||
bottom: 5%;
|
||||
z-index: 1;
|
||||
right: -7%;
|
||||
border: 1px #ffffff solid;
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<view @tap="backToTop" class="u-back-top" :class="['u-back-top--mode--' + mode]" :style="[{
|
||||
bottom: bottom + 'rpx',
|
||||
right: right + 'rpx',
|
||||
borderRadius: mode == 'circle' ? '10000rpx' : '8rpx',
|
||||
zIndex: uZIndex,
|
||||
opacity: opacity
|
||||
}, customStyle]">
|
||||
<view class="u-back-top__content" v-if="!$slots.default && !$slots.$default">
|
||||
<u-icon @click="backToTop" :name="icon" :custom-style="iconStyle"></u-icon>
|
||||
<view class="u-back-top__content__tips">
|
||||
{{tips}}
|
||||
</view>
|
||||
</view>
|
||||
<slot v-else />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'u-back-top',
|
||||
props: {
|
||||
// 返回顶部的形状,circle-圆形,square-方形
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 自定义图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'arrow-upward'
|
||||
},
|
||||
// 提示文字
|
||||
tips: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 返回顶部滚动时间
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 100
|
||||
},
|
||||
// 滚动距离
|
||||
scrollTop: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 距离顶部多少距离显示,单位rpx
|
||||
top: {
|
||||
type: [Number, String],
|
||||
default: 400
|
||||
},
|
||||
// 返回顶部按钮到底部的距离,单位rpx
|
||||
bottom: {
|
||||
type: [Number, String],
|
||||
default: 200
|
||||
},
|
||||
// 返回顶部按钮到右边的距离,单位rpx
|
||||
right: {
|
||||
type: [Number, String],
|
||||
default: 40
|
||||
},
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: '9'
|
||||
},
|
||||
// 图标的样式,对象形式
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
color: '#909399',
|
||||
fontSize: '38rpx'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 整个组件的样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showBackTop(nVal, oVal) {
|
||||
// 当组件的显示与隐藏状态发生跳变时,修改组件的层级和不透明度
|
||||
// 让组件有显示和消失的动画效果,如果用v-if控制组件状态,将无设置动画效果
|
||||
if(nVal) {
|
||||
this.uZIndex = this.zIndex;
|
||||
this.opacity = 1;
|
||||
} else {
|
||||
this.uZIndex = -1;
|
||||
this.opacity = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showBackTop() {
|
||||
// 由于scrollTop为页面的滚动距离,默认为px单位,这里将用于传入的top(rpx)值
|
||||
// 转为px用于比较,如果滚动条到顶的距离大于设定的距离,就显示返回顶部的按钮
|
||||
return this.scrollTop > uni.upx2px(this.top);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 不透明度,为了让组件有一个显示和隐藏的过渡动画
|
||||
opacity: 0,
|
||||
// 组件的z-index值,隐藏时设置为-1,就会看不到
|
||||
uZIndex: -1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
backToTop() {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: this.duration
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-back-top {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
@include vue-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
background-color: #E1E1E1;
|
||||
color: $u-content-color;
|
||||
align-items: center;
|
||||
transition: opacity 0.4s;
|
||||
|
||||
&__content {
|
||||
@include vue-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__tips {
|
||||
font-size: 24rpx;
|
||||
transform: scale(0.8);
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,216 @@
|
|||
<template>
|
||||
<view v-if="show" class="u-badge" :class="[
|
||||
isDot ? 'u-badge-dot' : '',
|
||||
size == 'mini' ? 'u-badge-mini' : '',
|
||||
type ? 'u-badge--bg--' + type : ''
|
||||
]" :style="[{
|
||||
top: offset[0] + 'rpx',
|
||||
right: offset[1] + 'rpx',
|
||||
fontSize: fontSize + 'rpx',
|
||||
position: absolute ? 'absolute' : 'static',
|
||||
color: color,
|
||||
backgroundColor: bgColor
|
||||
}, boxStyle]"
|
||||
>
|
||||
{{showText}}
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* badge 角标
|
||||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||
* @tutorial https://www.uviewui.com/components/badge.html
|
||||
* @property {String Number} count 展示的数字,大于 overflowCount 时显示为 ${overflowCount}+,为0且show-zero为false时隐藏
|
||||
* @property {Boolean} is-dot 不展示数字,只有一个小点(默认false)
|
||||
* @property {Boolean} absolute 组件是否绝对定位,为true时,offset参数才有效(默认true)
|
||||
* @property {String Number} overflow-count 展示封顶的数字值(默认99)
|
||||
* @property {String} type 使用预设的背景颜色(默认error)
|
||||
* @property {Boolean} show-zero 当数值为 0 时,是否展示 Badge(默认false)
|
||||
* @property {String} size Badge的尺寸,设为mini会得到小一号的Badge(默认default)
|
||||
* @property {Array} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,单位rpx。absolute为true时有效(默认[20, 20])
|
||||
* @property {String} color 字体颜色(默认#ffffff)
|
||||
* @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
|
||||
* @property {Boolean} is-center 组件中心点是否和父组件右上角重合,优先级比offset高,如设置,offset参数会失效(默认false)
|
||||
* @example <u-badge type="error" count="7"></u-badge>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-badge',
|
||||
props: {
|
||||
// primary,warning,success,error,info
|
||||
type: {
|
||||
type: String,
|
||||
default: 'error'
|
||||
},
|
||||
// default, mini
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
//是否是圆点
|
||||
isDot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示的数值内容
|
||||
count: {
|
||||
type: [Number, String],
|
||||
},
|
||||
// 展示封顶的数字值
|
||||
overflowCount: {
|
||||
type: Number,
|
||||
default: 99
|
||||
},
|
||||
// 当数值为 0 时,是否展示 Badge
|
||||
showZero: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 位置偏移
|
||||
offset: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [20, 20]
|
||||
}
|
||||
},
|
||||
// 是否开启绝对定位,开启了offset才会起作用
|
||||
absolute: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: '24'
|
||||
},
|
||||
// 字体演示
|
||||
color: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// badge的背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否让badge组件的中心点和父组件右上角重合,配置的话,offset将会失效
|
||||
isCenter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否将badge中心与父组件右上角重合
|
||||
boxStyle() {
|
||||
let style = {};
|
||||
if(this.isCenter) {
|
||||
style.top = 0;
|
||||
style.right = 0;
|
||||
// Y轴-50%,意味着badge向上移动了badge自身高度一半,X轴50%,意味着向右移动了自身宽度一半
|
||||
style.transform = "translateY(-50%) translateX(50%)";
|
||||
} else {
|
||||
style.top = this.offset[0] + 'rpx';
|
||||
style.right = this.offset[1] + 'rpx';
|
||||
style.transform = "translateY(0) translateX(0)";
|
||||
}
|
||||
// 如果尺寸为mini,后接上scal()
|
||||
if(this.size == 'mini') {
|
||||
style.transform = style.transform + " scale(0.8)";
|
||||
}
|
||||
return style;
|
||||
},
|
||||
// isDot类型时,不显示文字
|
||||
showText() {
|
||||
if(this.isDot) return '';
|
||||
else {
|
||||
if(this.count > this.overflowCount) return `${this.overflowCount}+`;
|
||||
else return this.count;
|
||||
}
|
||||
},
|
||||
// 是否显示组件
|
||||
show() {
|
||||
// 如果count的值为0,并且showZero设置为false,不显示组件
|
||||
if(this.count == 0 && this.showZero == false) return false;
|
||||
else return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-badge {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 24rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 100rpx;
|
||||
z-index: 9;
|
||||
|
||||
&--bg--primary {
|
||||
background-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--bg--error {
|
||||
background-color: $u-type-error;
|
||||
}
|
||||
|
||||
&--bg--success {
|
||||
background-color: $u-type-success;
|
||||
}
|
||||
|
||||
&--bg--info {
|
||||
background-color: $u-type-info;
|
||||
}
|
||||
|
||||
&--bg--warning {
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
|
||||
.u-badge-dot {
|
||||
height: 16rpx;
|
||||
width: 16rpx;
|
||||
border-radius: 100rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.u-badge-mini {
|
||||
transform: scale(0.8);
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
// .u-primary {
|
||||
// background: $u-type-primary;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
// .u-error {
|
||||
// background: $u-type-error;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
// .u-warning {
|
||||
// background: $u-type-warning;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
// .u-success {
|
||||
// background: $u-type-success;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
// .u-black {
|
||||
// background: #585858;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
.u-info {
|
||||
background-color: $u-type-info;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,596 @@
|
|||
<template>
|
||||
<button
|
||||
id="u-wave-btn"
|
||||
class="u-btn u-line-1 u-fix-ios-appearance"
|
||||
:class="[
|
||||
'u-size-' + size,
|
||||
plain ? 'u-btn--' + type + '--plain' : '',
|
||||
loading ? 'u-loading' : '',
|
||||
shape == 'circle' ? 'u-round-circle' : '',
|
||||
hairLine ? showHairLineBorder : 'u-btn--bold-border',
|
||||
'u-btn--' + type,
|
||||
disabled ? `u-btn--${type}--disabled` : '',
|
||||
]"
|
||||
:hover-start-time="Number(hoverStartTime)"
|
||||
:hover-stay-time="Number(hoverStayTime)"
|
||||
:disabled="disabled"
|
||||
: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"
|
||||
:style="[customStyle, {
|
||||
overflow: ripple ? 'hidden' : 'visible'
|
||||
}]"
|
||||
@tap.stop="click($event)"
|
||||
:hover-class="getHoverClass"
|
||||
:loading="loading"
|
||||
>
|
||||
<slot></slot>
|
||||
<view
|
||||
v-if="ripple"
|
||||
class="u-wave-ripple"
|
||||
:class="[waveActive ? 'u-wave-active' : '']"
|
||||
:style="{
|
||||
top: rippleTop + 'px',
|
||||
left: rippleLeft + 'px',
|
||||
width: fields.targetWidth + 'px',
|
||||
height: fields.targetWidth + 'px',
|
||||
'background-color': rippleBgColor || 'rgba(0, 0, 0, 0.15)'
|
||||
}"
|
||||
></view>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* button 按钮
|
||||
* @description Button 按钮
|
||||
* @tutorial https://www.uviewui.com/components/button.html
|
||||
* @property {String} size 按钮的大小
|
||||
* @property {Boolean} ripple 是否开启点击水波纹效果
|
||||
* @property {String} ripple-bg-color 水波纹的背景色,ripple为true时有效
|
||||
* @property {String} type 按钮的样式类型
|
||||
* @property {Boolean} plain 按钮是否镂空,背景色透明
|
||||
* @property {Boolean} disabled 是否禁用
|
||||
* @property {Boolean} hair-line 是否显示按钮的细边框(默认true)
|
||||
* @property {Boolean} shape 按钮外观形状,见文档说明
|
||||
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈)
|
||||
* @property {String} form-type 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
* @property {String} open-type 开放能力
|
||||
* @property {String} data-name 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
* @property {String} hover-class 指定按钮按下去的样式类。当 hover-class="none" 时,没有点击态效果(App-nvue 平台暂不支持)
|
||||
* @property {Number} hover-start-time 按住后多久出现点击态,单位毫秒
|
||||
* @property {Number} hover-stay-time 手指松开后点击态保留时间,单位毫秒
|
||||
* @property {Object} custom-style 对按钮的自定义样式,对象形式,见文档说明
|
||||
* @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',
|
||||
props: {
|
||||
// 是否细边框
|
||||
hairLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 按钮的预置样式,default,primary,error,warning,success
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
// 按钮尺寸,default,medium,mini
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
// 按钮形状,circle(两边为半圆),square(带圆角)
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
// 按钮是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁止状态
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否加载中
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 开放能力,具体请看uniapp稳定关于button组件部分说明
|
||||
// https://uniapp.dcloud.io/component/button
|
||||
openType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
// 取值为submit(提交表单),reset(重置表单)
|
||||
formType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
|
||||
// 只微信小程序、QQ小程序有效
|
||||
appParameter: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
|
||||
hoverStopPropagation: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
|
||||
lang: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
// 会话来源,open-type="contact"时有效。只微信小程序有效
|
||||
sessionFrom: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 会话内消息卡片标题,open-type="contact"时有效
|
||||
// 默认当前标题,只微信小程序有效
|
||||
sendMessageTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
|
||||
// 默认当前分享路径,只微信小程序有效
|
||||
sendMessagePath: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 会话内消息卡片图片,open-type="contact"时有效
|
||||
// 默认当前页面截图,只微信小程序有效
|
||||
sendMessageImg: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
|
||||
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
|
||||
showMessageCard: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 手指按(触摸)按钮时按钮时的背景颜色
|
||||
hoverBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 水波纹的背景颜色
|
||||
rippleBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否开启水波纹效果
|
||||
ripple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 按下的类名
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 自定义样式,对象形式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
dataName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 节流,一定时间内只能触发一次
|
||||
throttleTime: {
|
||||
type: [String, Number],
|
||||
default: 1000
|
||||
},
|
||||
// 按住后多久出现点击态,单位毫秒
|
||||
hoverStartTime: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
// 手指松开后点击态保留时间,单位毫秒
|
||||
hoverStayTime: {
|
||||
type: [String, Number],
|
||||
default: 150
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 当没有传bgColor变量时,按钮按下去的颜色类名
|
||||
getHoverClass() {
|
||||
// 如果开启水波纹效果,则不启用hover-class效果
|
||||
if (this.loading || this.disabled || this.ripple || this.hoverClass) return '';
|
||||
let hoverClass = '';
|
||||
hoverClass = this.plain ? 'u-' + this.type + '-plain-hover' : 'u-' + this.type + '-hover';
|
||||
return hoverClass;
|
||||
},
|
||||
// 在'primary', 'success', 'error', 'warning'类型下,不显示边框,否则会造成四角有毛刺现象
|
||||
showHairLineBorder() {
|
||||
if (['primary', 'success', 'error', 'warning'].indexOf(this.type) >= 0 && !this.plain) {
|
||||
return '';
|
||||
} else {
|
||||
return 'u-hairline-border';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rippleTop: 0, // 水波纹的起点Y坐标到按钮上边界的距离
|
||||
rippleLeft: 0, // 水波纹起点X坐标到按钮左边界的距离
|
||||
fields: {}, // 波纹按钮节点信息
|
||||
waveActive: false // 激活水波纹
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 按钮点击
|
||||
click(e) {
|
||||
// 进行节流控制,每this.throttle毫秒内,只在开始处执行
|
||||
this.$u.throttle(() => {
|
||||
// 如果按钮时disabled和loading状态,不触发水波纹效果
|
||||
if (this.loading === true || this.disabled === true) return;
|
||||
// 是否开启水波纹效果
|
||||
if (this.ripple) {
|
||||
// 每次点击时,移除上一次的类,再次添加,才能触发动画效果
|
||||
this.waveActive = false;
|
||||
this.$nextTick(function() {
|
||||
this.getWaveQuery(e);
|
||||
});
|
||||
}
|
||||
this.$emit('click', e);
|
||||
}, this.throttleTime);
|
||||
},
|
||||
// 查询按钮的节点信息
|
||||
getWaveQuery(e) {
|
||||
this.getElQuery().then(res => {
|
||||
// 查询返回的是一个数组节点
|
||||
let data = res[0];
|
||||
// 查询不到节点信息,不操作
|
||||
if (!data.width || !data.width) return;
|
||||
// 水波纹的最终形态是一个正方形(通过border-radius让其变为一个圆形),这里要保证正方形的边长等于按钮的最长边
|
||||
// 最终的方形(变换后的圆形)才能覆盖整个按钮
|
||||
data.targetWidth = data.height > data.width ? data.height : data.width;
|
||||
if (!data.targetWidth) return;
|
||||
this.fields = data;
|
||||
let touchesX = '',
|
||||
touchesY = '';
|
||||
// #ifdef MP-BAIDU
|
||||
touchesX = e.changedTouches[0].clientX;
|
||||
touchesY = e.changedTouches[0].clientY;
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
touchesX = e.detail.clientX;
|
||||
touchesY = e.detail.clientY;
|
||||
// #endif
|
||||
// #ifndef MP-BAIDU || MP-ALIPAY
|
||||
touchesX = e.touches[0].clientX;
|
||||
touchesY = e.touches[0].clientY;
|
||||
// #endif
|
||||
// 获取触摸点相对于按钮上边和左边的x和y坐标,原理是通过屏幕的触摸点(touchesY),减去按钮的上边界data.top
|
||||
// 但是由于`transform-origin`默认是center,所以这里再减去半径才是水波纹view应该的位置
|
||||
// 总的来说,就是把水波纹的矩形(变换后的圆形)的中心点,移动到我们的触摸点位置
|
||||
this.rippleTop = touchesY - data.top - data.targetWidth / 2;
|
||||
this.rippleLeft = touchesX - data.left - data.targetWidth / 2;
|
||||
this.$nextTick(() => {
|
||||
this.waveActive = true;
|
||||
});
|
||||
});
|
||||
},
|
||||
// 获取节点信息
|
||||
getElQuery() {
|
||||
return new Promise(resolve => {
|
||||
let queryInfo = '';
|
||||
// 获取元素节点信息,请查看uniapp相关文档
|
||||
// https://uniapp.dcloud.io/api/ui/nodes-info?id=nodesrefboundingclientrect
|
||||
queryInfo = uni.createSelectorQuery().in(this);
|
||||
//#ifdef MP-ALIPAY
|
||||
queryInfo = uni.createSelectorQuery();
|
||||
//#endif
|
||||
queryInfo.select('.u-btn').boundingClientRect();
|
||||
queryInfo.exec(data => {
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 下面为对接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 scoped lang="scss">
|
||||
@import '../../libs/css/style.components.scss';
|
||||
.u-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.u-btn {
|
||||
position: relative;
|
||||
border: 0;
|
||||
//border-radius: 10rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
// 避免边框某些场景可能被“裁剪”,不能设置为hidden
|
||||
overflow: visible;
|
||||
line-height: 1;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 0 40rpx;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.15s;
|
||||
|
||||
&--bold-border {
|
||||
border: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
&--default {
|
||||
color: $u-content-color;
|
||||
border-color: #c0c4cc;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-primary;
|
||||
background-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-success;
|
||||
background-color: $u-type-success;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-error;
|
||||
background-color: $u-type-error;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: #ffffff;
|
||||
border-color: $u-type-warning;
|
||||
background-color: $u-type-warning;
|
||||
}
|
||||
|
||||
&--default--disabled {
|
||||
color: #ffffff;
|
||||
border-color: #e4e7ed;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
&--primary--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-primary-disabled!important;
|
||||
background-color: $u-type-primary-disabled!important;
|
||||
}
|
||||
|
||||
&--success--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-success-disabled!important;
|
||||
background-color: $u-type-success-disabled!important;
|
||||
}
|
||||
|
||||
&--error--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-error-disabled!important;
|
||||
background-color: $u-type-error-disabled!important;
|
||||
}
|
||||
|
||||
&--warning--disabled {
|
||||
color: #ffffff!important;
|
||||
border-color: $u-type-warning-disabled!important;
|
||||
background-color: $u-type-warning-disabled!important;
|
||||
}
|
||||
|
||||
&--primary--plain {
|
||||
color: $u-type-primary!important;
|
||||
border-color: $u-type-primary-disabled!important;
|
||||
background-color: $u-type-primary-light!important;
|
||||
}
|
||||
|
||||
&--success--plain {
|
||||
color: $u-type-success!important;
|
||||
border-color: $u-type-success-disabled!important;
|
||||
background-color: $u-type-success-light!important;
|
||||
}
|
||||
|
||||
&--error--plain {
|
||||
color: $u-type-error!important;
|
||||
border-color: $u-type-error-disabled!important;
|
||||
background-color: $u-type-error-light!important;
|
||||
}
|
||||
|
||||
&--warning--plain {
|
||||
color: $u-type-warning!important;
|
||||
border-color: $u-type-warning-disabled!important;
|
||||
background-color: $u-type-warning-light!important;
|
||||
}
|
||||
}
|
||||
|
||||
.u-hairline-border:after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
// 设置为border-box,意味着下面的scale缩小为0.5,实际上缩小的是伪元素的内容(border-box意味着内容不含border)
|
||||
box-sizing: border-box;
|
||||
// 中心点作为变形(scale())的原点
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 199.8%;
|
||||
height: 199.7%;
|
||||
-webkit-transform: scale(0.5, 0.5);
|
||||
transform: scale(0.5, 0.5);
|
||||
border: 1px solid currentColor;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.u-wave-ripple {
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background-clip: padding-box;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
transform: scale(0);
|
||||
opacity: 1;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.u-wave-ripple.u-wave-active {
|
||||
opacity: 0;
|
||||
transform: scale(2);
|
||||
transition: opacity 1s linear, transform 0.4s linear;
|
||||
}
|
||||
|
||||
.u-round-circle {
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.u-round-circle::after {
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.u-loading::after {
|
||||
background-color: hsla(0, 0%, 100%, 0.35);
|
||||
}
|
||||
|
||||
.u-size-default {
|
||||
font-size: 30rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
}
|
||||
|
||||
.u-size-medium {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
width: auto;
|
||||
font-size: 26rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
padding: 0 80rpx;
|
||||
}
|
||||
|
||||
.u-size-mini {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
width: auto;
|
||||
font-size: 22rpx;
|
||||
padding-top: 1px;
|
||||
height: 50rpx;
|
||||
line-height: 50rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.u-primary-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-primary-dark !important;
|
||||
}
|
||||
|
||||
.u-default-plain-hover {
|
||||
color: $u-type-primary-dark !important;
|
||||
background: $u-type-primary-light !important;
|
||||
}
|
||||
|
||||
.u-success-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-success-dark !important;
|
||||
}
|
||||
|
||||
.u-warning-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-warning-dark !important;
|
||||
}
|
||||
|
||||
.u-error-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-error-dark !important;
|
||||
}
|
||||
|
||||
.u-info-plain-hover {
|
||||
color: #ffffff !important;
|
||||
background: $u-type-info-dark !important;
|
||||
}
|
||||
|
||||
.u-default-hover {
|
||||
color: $u-type-primary-dark !important;
|
||||
border-color: $u-type-primary-dark !important;
|
||||
background-color: $u-type-primary-light !important;
|
||||
}
|
||||
|
||||
.u-primary-hover {
|
||||
background: $u-type-primary-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-success-hover {
|
||||
background: $u-type-success-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-info-hover {
|
||||
background: $u-type-info-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-warning-hover {
|
||||
background: $u-type-warning-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.u-error-hover {
|
||||
background: $u-type-error-dark !important;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,639 @@
|
|||
<template>
|
||||
<u-popup closeable :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex" :border-radius="borderRadius" :closeable="closeable">
|
||||
<view class="u-calendar">
|
||||
<view class="u-calendar__header">
|
||||
<view class="u-calendar__header__text" v-if="!$slots['tooltip']">
|
||||
{{toolTip}}
|
||||
</view>
|
||||
<slot v-else name="tooltip" />
|
||||
</view>
|
||||
<view class="u-calendar__action u-flex u-row-center">
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeYear" name="arrow-left-double" :color="yearArrowColor" @click="changeYearHandler(0)"></u-icon>
|
||||
</view>
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeMonth" name="arrow-left" :color="monthArrowColor" @click="changeMonthHandler(0)"></u-icon>
|
||||
</view>
|
||||
<view class="u-calendar__action__text">{{ showTitle }}</view>
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeMonth" name="arrow-right" :color="monthArrowColor" @click="changeMonthHandler(1)"></u-icon>
|
||||
</view>
|
||||
<view class="u-calendar__action__icon">
|
||||
<u-icon v-if="changeYear" name="arrow-right-double" :color="yearArrowColor" @click="changeYearHandler(1)"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-calendar__week-day">
|
||||
<view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">{{item}}</view>
|
||||
</view>
|
||||
<view class="u-calendar__content">
|
||||
<!-- 前置空白部分 -->
|
||||
<block v-for="(item, index) in weekdayArr" :key="index">
|
||||
<view class="u-calendar__content__item"></view>
|
||||
</block>
|
||||
<view class="u-calendar__content__item" :class="{
|
||||
'u-hover-class':openDisAbled(year,month,index+1),
|
||||
'u-calendar__content--start-date': (mode == 'range' && startDate==`${year}-${month}-${index+1}`) || mode== 'date',
|
||||
'u-calendar__content--end-date':(mode== 'range' && endDate==`${year}-${month}-${index+1}`) || mode == 'date'
|
||||
}" :style="{backgroundColor: getColor(index,1)}" v-for="(item, index) in daysArr" :key="index"
|
||||
@tap="dateClick(index)">
|
||||
<view class="u-calendar__content__item__inner" :style="{color: getColor(index,2)}">
|
||||
<view>{{ index + 1 }}</view>
|
||||
</view>
|
||||
<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && startDate==`${year}-${month}-${index+1}` && startDate!=endDate">{{startText}}</view>
|
||||
<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && endDate==`${year}-${month}-${index+1}`">{{endText}}</view>
|
||||
</view>
|
||||
<view class="u-calendar__content__bg-month">{{month}}</view>
|
||||
</view>
|
||||
<view class="u-calendar__bottom">
|
||||
<view class="u-calendar__bottom__choose">
|
||||
<text>{{mode == 'date' ? activeDate : startDate}}</text>
|
||||
<text v-if="endDate">至{{endDate}}</text>
|
||||
</view>
|
||||
<view class="u-calendar__bottom__btn">
|
||||
<u-button :type="btnType" shape="circle" size="default" @click="btnFix(false)">确定</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* calendar 日历
|
||||
* @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。
|
||||
* @tutorial http://uviewui.com/components/calendar.html
|
||||
* @property {String} mode 选择日期的模式,date-为单个日期,range-为选择日期范围
|
||||
* @property {Boolean} v-model 布尔值变量,用于控制日历的弹出与收起
|
||||
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
|
||||
* @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true)
|
||||
* @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true)
|
||||
* @property {String Number} max-year 可切换的最大年份(默认2050)
|
||||
* @property {String Number} min-year 可切换的最小年份(默认1950)
|
||||
* @property {String Number} min-date 最小可选日期(默认1950-01-01)
|
||||
* @property {String Number} max-date 最大可选日期(默认当前日期)
|
||||
* @property {String Number} 弹窗顶部左右两边的圆角值,单位rpx(默认20)
|
||||
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true)
|
||||
* @property {String} month-arrow-color 月份切换按钮箭头颜色(默认#606266)
|
||||
* @property {String} year-arrow-color 年份切换按钮箭头颜色(默认#909399)
|
||||
* @property {String} color 日期字体的默认颜色(默认#303133)
|
||||
* @property {String} active-bg-color 起始/结束日期按钮的背景色(默认#2979ff)
|
||||
* @property {String Number} z-index 弹出时的z-index值(默认10075)
|
||||
* @property {String} active-color 起始/结束日期按钮的字体颜色(默认#ffffff)
|
||||
* @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13))
|
||||
* @property {String} range-color 选择范围内字体颜色(默认#2979ff)
|
||||
* @property {String} start-text 起始日期底部的提示文字(默认 '开始')
|
||||
* @property {String} end-text 结束日期底部的提示文字(默认 '结束')
|
||||
* @property {String} btn-type 底部确定按钮的主题(默认 'primary')
|
||||
* @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期')
|
||||
* @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
|
||||
* @example <u-calendar v-model="show" :mode="mode"></u-calendar>
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'u-calendar',
|
||||
props: {
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许通过点击遮罩关闭Picker
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 通过双向绑定控制组件的弹出与收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹出的z-index值
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否允许切换年份
|
||||
changeYear: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否允许切换月份
|
||||
changeMonth: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// date-单个日期选择,range-开始日期+结束日期选择
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'date'
|
||||
},
|
||||
// 可切换的最大年份
|
||||
maxYear: {
|
||||
type: [Number, String],
|
||||
default: 2050
|
||||
},
|
||||
// 可切换的最小年份
|
||||
minYear: {
|
||||
type: [Number, String],
|
||||
default: 1950
|
||||
},
|
||||
// 最小可选日期(不在范围内日期禁用不可选)
|
||||
minDate: {
|
||||
type: [Number, String],
|
||||
default: '1950-01-01'
|
||||
},
|
||||
/**
|
||||
* 最大可选日期
|
||||
* 默认最大值为今天,之后的日期不可选
|
||||
* 2030-12-31
|
||||
* */
|
||||
maxDate: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 弹窗顶部左右两边的圆角值
|
||||
borderRadius: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
// 月份切换按钮箭头颜色
|
||||
monthArrowColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 年份切换按钮箭头颜色
|
||||
yearArrowColor: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 默认日期字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 选中|起始结束日期背景色
|
||||
activeBgColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 选中|起始结束日期字体颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// 范围内日期背景色
|
||||
rangeBgColor: {
|
||||
type: String,
|
||||
default: 'rgba(41,121,255,0.13)'
|
||||
},
|
||||
// 范围内日期字体颜色
|
||||
rangeColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// mode=range时生效,起始日期自定义文案
|
||||
startText: {
|
||||
type: String,
|
||||
default: '开始'
|
||||
},
|
||||
// mode=range时生效,结束日期自定义文案
|
||||
endText: {
|
||||
type: String,
|
||||
default: '结束'
|
||||
},
|
||||
//按钮样式类型
|
||||
btnType: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
// 当前选中日期带选中效果
|
||||
isActiveCurrent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 切换年月是否触发事件 mode=date时生效
|
||||
isChange: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示右上角的关闭图标
|
||||
closeable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 顶部的提示文字
|
||||
toolTip: {
|
||||
type: String,
|
||||
default: '选择日期'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 星期几,值为1-7
|
||||
weekday: 1,
|
||||
weekdayArr:[],
|
||||
// 当前月有多少天
|
||||
days: 0,
|
||||
daysArr:[],
|
||||
showTitle: '',
|
||||
year: 2020,
|
||||
month: 0,
|
||||
day: 0,
|
||||
startYear: 0,
|
||||
startMonth: 0,
|
||||
startDay: 0,
|
||||
endYear: 0,
|
||||
endMonth: 0,
|
||||
endDay: 0,
|
||||
today: '',
|
||||
activeDate: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
isStart: true,
|
||||
min: null,
|
||||
max: null,
|
||||
weekDayZh: ['日', '一', '二', '三', '四', '五', '六']
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dataChange() {
|
||||
return `${this.mode}-${this.minDate}-${this.maxDate}`;
|
||||
},
|
||||
uZIndex() {
|
||||
// 如果用户有传递z-index值,优先使用
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dataChange(val) {
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
getColor(index, type) {
|
||||
let color = type == 1 ? '' : this.color;
|
||||
let day = index + 1
|
||||
let date = `${this.year}-${this.month}-${day}`
|
||||
let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
|
||||
let start = this.startDate.replace(/\-/g, '/')
|
||||
let end = this.endDate.replace(/\-/g, '/')
|
||||
if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
|
||||
color = type == 1 ? this.activeBgColor : this.activeColor;
|
||||
} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
|
||||
color = type == 1 ? this.rangeBgColor : this.rangeColor;
|
||||
}
|
||||
return color;
|
||||
},
|
||||
init() {
|
||||
let now = new Date();
|
||||
this.year = now.getFullYear();
|
||||
this.month = now.getMonth() + 1;
|
||||
this.day = now.getDate();
|
||||
this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
|
||||
this.activeDate = this.today;
|
||||
this.min = this.initDate(this.minDate);
|
||||
this.max = this.initDate(this.maxDate || this.today);
|
||||
this.startDate = "";
|
||||
this.startYear = 0;
|
||||
this.startMonth = 0;
|
||||
this.startDay = 0;
|
||||
this.endYear = 0;
|
||||
this.endMonth = 0;
|
||||
this.endDay = 0;
|
||||
this.endDate = "";
|
||||
this.isStart = true;
|
||||
this.changeData();
|
||||
},
|
||||
//日期处理
|
||||
initDate(date) {
|
||||
let fdate = date.split('-');
|
||||
return {
|
||||
year: Number(fdate[0] || 1920),
|
||||
month: Number(fdate[1] || 1),
|
||||
day: Number(fdate[2] || 1)
|
||||
}
|
||||
},
|
||||
openDisAbled: function(year, month, day) {
|
||||
let bool = true;
|
||||
let date = `${year}/${month}/${day}`;
|
||||
// let today = this.today.replace(/\-/g, '/');
|
||||
let min = `${this.min.year}/${this.min.month}/${this.min.day}`;
|
||||
let max = `${this.max.year}/${this.max.month}/${this.max.day}`;
|
||||
let timestamp = new Date(date).getTime();
|
||||
if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
|
||||
bool = false;
|
||||
}
|
||||
return bool;
|
||||
},
|
||||
generateArray: function(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start);
|
||||
},
|
||||
formatNum: function(num) {
|
||||
return num < 10 ? '0' + num : num + '';
|
||||
},
|
||||
//一个月有多少天
|
||||
getMonthDay(year, month) {
|
||||
let days = new Date(year, month, 0).getDate();
|
||||
return days;
|
||||
},
|
||||
getWeekday(year, month) {
|
||||
let date = new Date(`${year}/${month}/01 00:00:00`);
|
||||
return date.getDay();
|
||||
},
|
||||
checkRange(year) {
|
||||
let overstep = false;
|
||||
if (year < this.minYear || year > this.maxYear) {
|
||||
uni.showToast({
|
||||
title: "日期超出范围啦~",
|
||||
icon: 'none'
|
||||
})
|
||||
overstep = true;
|
||||
}
|
||||
return overstep;
|
||||
},
|
||||
changeMonthHandler(isAdd) {
|
||||
if (isAdd) {
|
||||
let month = this.month + 1;
|
||||
let year = month > 12 ? this.year + 1 : this.year;
|
||||
if (!this.checkRange(year)) {
|
||||
this.month = month > 12 ? 1 : month;
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
|
||||
} else {
|
||||
let month = this.month - 1;
|
||||
let year = month < 1 ? this.year - 1 : this.year;
|
||||
if (!this.checkRange(year)) {
|
||||
this.month = month < 1 ? 12 : month;
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
}
|
||||
},
|
||||
changeYearHandler(isAdd) {
|
||||
let year = isAdd ? this.year + 1 : this.year - 1;
|
||||
if (!this.checkRange(year)) {
|
||||
this.year = year;
|
||||
this.changeData();
|
||||
}
|
||||
},
|
||||
changeData() {
|
||||
this.days = this.getMonthDay(this.year, this.month);
|
||||
this.daysArr=this.generateArray(1,this.days)
|
||||
this.weekday = this.getWeekday(this.year, this.month);
|
||||
this.weekdayArr=this.generateArray(1,this.weekday)
|
||||
this.showTitle = `${this.year}年${this.month}月`;
|
||||
if (this.isChange && this.mode == 'date') {
|
||||
this.btnFix(true);
|
||||
}
|
||||
},
|
||||
dateClick: function(day) {
|
||||
day += 1;
|
||||
if (!this.openDisAbled(this.year, this.month, day)) {
|
||||
this.day = day;
|
||||
let date = `${this.year}-${this.month}-${day}`;
|
||||
if (this.mode == 'date') {
|
||||
this.activeDate = date;
|
||||
} else {
|
||||
let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(/\-/g, '/')).getTime()
|
||||
if (this.isStart || compare) {
|
||||
this.startDate = date;
|
||||
this.startYear = this.year;
|
||||
this.startMonth = this.month;
|
||||
this.startDay = this.day;
|
||||
this.endYear = 0;
|
||||
this.endMonth = 0;
|
||||
this.endDay = 0;
|
||||
this.endDate = "";
|
||||
this.activeDate = "";
|
||||
this.isStart = false;
|
||||
} else {
|
||||
this.endDate = date;
|
||||
this.endYear = this.year;
|
||||
this.endMonth = this.month;
|
||||
this.endDay = this.day;
|
||||
this.isStart = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
close() {
|
||||
// 修改通过v-model绑定的父组件变量的值为false,从而隐藏日历弹窗
|
||||
this.$emit('input', false);
|
||||
},
|
||||
getWeekText(date) {
|
||||
date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
|
||||
let week = date.getDay();
|
||||
return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
|
||||
},
|
||||
btnFix(show) {
|
||||
if (!show) {
|
||||
this.close();
|
||||
}
|
||||
if (this.mode == 'date') {
|
||||
let arr = this.activeDate.split('-')
|
||||
let year = this.isChange ? this.year : Number(arr[0]);
|
||||
let month = this.isChange ? this.month : Number(arr[1]);
|
||||
let day = this.isChange ? this.day : Number(arr[2]);
|
||||
//当前月有多少天
|
||||
let days = this.getMonthDay(year, month);
|
||||
let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`;
|
||||
let weekText = this.getWeekText(result);
|
||||
let isToday = false;
|
||||
if (`${year}-${month}-${day}` == this.today) {
|
||||
//今天
|
||||
isToday = true;
|
||||
}
|
||||
this.$emit('change', {
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
days: days,
|
||||
result: result,
|
||||
week: weekText,
|
||||
isToday: isToday,
|
||||
// switch: show //是否是切换年月操作
|
||||
});
|
||||
} else {
|
||||
if (!this.startDate || !this.endDate) return;
|
||||
let startMonth = this.formatNum(this.startMonth);
|
||||
let startDay = this.formatNum(this.startDay);
|
||||
let startDate = `${this.startYear}-${startMonth}-${startDay}`;
|
||||
let startWeek = this.getWeekText(startDate)
|
||||
|
||||
let endMonth = this.formatNum(this.endMonth);
|
||||
let endDay = this.formatNum(this.endDay);
|
||||
let endDate = `${this.endYear}-${endMonth}-${endDay}`;
|
||||
let endWeek = this.getWeekText(endDate);
|
||||
this.$emit('change', {
|
||||
startYear: this.startYear,
|
||||
startMonth: this.startMonth,
|
||||
startDay: this.startDay,
|
||||
startDate: startDate,
|
||||
startWeek: startWeek,
|
||||
endYear: this.endYear,
|
||||
endMonth: this.endMonth,
|
||||
endDay: this.endDay,
|
||||
endDate: endDate,
|
||||
endWeek: endWeek
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-calendar {
|
||||
color: $u-content-color;
|
||||
|
||||
&__header {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 30rpx;
|
||||
background-color: #fff;
|
||||
color: $u-main-color;
|
||||
|
||||
&__text {
|
||||
margin-top: 30rpx;
|
||||
padding: 0 60rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__action {
|
||||
padding: 40rpx 0 40rpx 0;
|
||||
|
||||
&__icon {
|
||||
margin: 0 16rpx;
|
||||
}
|
||||
|
||||
&__text {
|
||||
padding: 0 16rpx;
|
||||
color: $u-main-color;
|
||||
font-size: 32rpx;
|
||||
line-height: 32rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__week-day {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 0;
|
||||
overflow: hidden;
|
||||
|
||||
&__text {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
@include vue-flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 6px 0;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
|
||||
&--end-date {
|
||||
border-top-right-radius: 8rpx;
|
||||
border-bottom-right-radius: 8rpx;
|
||||
}
|
||||
|
||||
&--start-date {
|
||||
border-top-left-radius: 8rpx;
|
||||
border-bottom-left-radius: 8rpx;
|
||||
}
|
||||
|
||||
&__item {
|
||||
width: 14.2857%;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6px 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
&__inner {
|
||||
height: 84rpx;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
font-size: 32rpx;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
|
||||
&__desc {
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
transform: scale(0.75);
|
||||
transform-origin: center center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
bottom: 2rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__tips {
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
transform: scale(0.8);
|
||||
transform-origin: center center;
|
||||
text-align: center;
|
||||
bottom: 8rpx;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&__bg-month {
|
||||
position: absolute;
|
||||
font-size: 130px;
|
||||
line-height: 130px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #e4e7ed;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__bottom {
|
||||
width: 100%;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
padding: 0 40rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
|
||||
&__choose {
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,257 @@
|
|||
<template>
|
||||
<view class="u-keyboard" @touchmove.stop.prevent="() => {}">
|
||||
<view class="u-keyboard-grids">
|
||||
<block>
|
||||
<view class="u-keyboard-grids-item" v-for="(group, i) in abc ? EngKeyBoardList : areaList" :key="i">
|
||||
<view :hover-stay-time="100" @tap="carInputClick(i, j)" hover-class="u-carinput-hover" class="u-keyboard-grids-btn"
|
||||
v-for="(item, j) in group" :key="j">
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
<view @touchstart="backspaceClick" @touchend="clearTimer" :hover-stay-time="100" class="u-keyboard-back"
|
||||
hover-class="u-hover-class">
|
||||
<u-icon :size="38" name="backspace" :bold="true"></u-icon>
|
||||
</view>
|
||||
<view :hover-stay-time="100" class="u-keyboard-change" hover-class="u-carinput-hover" @tap="changeCarInputMode">
|
||||
<text class="zh" :class="[!abc ? 'active' : 'inactive']">中</text>
|
||||
/
|
||||
<text class="en" :class="[abc ? 'active' : 'inactive']">英</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "u-keyboard",
|
||||
props: {
|
||||
// 是否打乱键盘按键的顺序
|
||||
random: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
|
||||
abc: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
areaList() {
|
||||
let data = [
|
||||
'京',
|
||||
'沪',
|
||||
'粤',
|
||||
'津',
|
||||
'冀',
|
||||
'豫',
|
||||
'云',
|
||||
'辽',
|
||||
'黑',
|
||||
'湘',
|
||||
'皖',
|
||||
'鲁',
|
||||
'苏',
|
||||
'浙',
|
||||
'赣',
|
||||
'鄂',
|
||||
'桂',
|
||||
'甘',
|
||||
'晋',
|
||||
'陕',
|
||||
'蒙',
|
||||
'吉',
|
||||
'闽',
|
||||
'贵',
|
||||
'渝',
|
||||
'川',
|
||||
'青',
|
||||
'琼',
|
||||
'宁',
|
||||
'挂',
|
||||
'藏',
|
||||
'港',
|
||||
'澳',
|
||||
'新',
|
||||
'使',
|
||||
'学'
|
||||
];
|
||||
let tmp = [];
|
||||
// 打乱顺序
|
||||
if (this.random) data = this.$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 = this.$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];
|
||||
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/style.components.scss";
|
||||
|
||||
.u-keyboard-grids {
|
||||
background: rgb(215, 215, 217);
|
||||
padding: 24rpx 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.u-keyboard-grids-item {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-keyboard-grids-btn {
|
||||
text-decoration: none;
|
||||
width: 62rpx;
|
||||
flex: 0 0 64rpx;
|
||||
height: 80rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
font-size: 36rpx;
|
||||
text-align: center;
|
||||
line-height: 80rpx;
|
||||
background-color: #fff;
|
||||
margin: 8rpx 5rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx #888992;
|
||||
font-weight: 500;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-carinput-hover {
|
||||
background-color: rgb(185, 188, 195) !important;
|
||||
}
|
||||
|
||||
.u-keyboard-back {
|
||||
position: absolute;
|
||||
width: 96rpx;
|
||||
right: 22rpx;
|
||||
bottom: 32rpx;
|
||||
height: 80rpx;
|
||||
background-color: rgb(185, 188, 195);
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
border-radius: 8rpx;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 0rpx #888992;
|
||||
}
|
||||
|
||||
.u-keyboard-change {
|
||||
font-size: 24rpx;
|
||||
box-shadow: 0 2rpx 0rpx #888992;
|
||||
position: absolute;
|
||||
width: 96rpx;
|
||||
left: 22rpx;
|
||||
line-height: 1;
|
||||
bottom: 32rpx;
|
||||
height: 80rpx;
|
||||
background-color: #ffffff;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
border-radius: 8rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-keyboard-change .inactive.zh {
|
||||
transform: scale(0.85) translateY(-10rpx);
|
||||
}
|
||||
|
||||
.u-keyboard-change .inactive.en {
|
||||
transform: scale(0.85) translateY(10rpx);
|
||||
}
|
||||
|
||||
.u-keyboard-change .active {
|
||||
color: rgb(237, 112, 64);
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.u-keyboard-change .zh {
|
||||
transform: translateY(-10rpx);
|
||||
}
|
||||
|
||||
.u-keyboard-change .en {
|
||||
transform: translateY(10rpx);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,299 @@
|
|||
<template>
|
||||
<view
|
||||
class="u-card"
|
||||
@tap.stop="click"
|
||||
:class="{ 'u-border': border, 'u-card-full': full, 'u-card--border': borderRadius > 0 }"
|
||||
:style="{
|
||||
borderRadius: borderRadius + 'rpx',
|
||||
margin: margin,
|
||||
boxShadow: boxShadow
|
||||
}"
|
||||
>
|
||||
<view
|
||||
v-if="showHead"
|
||||
class="u-card__head"
|
||||
:style="[{padding: padding + 'rpx'}, headStyle]"
|
||||
:class="{
|
||||
'u-border-bottom': headBorderBottom
|
||||
}"
|
||||
@tap="headClick"
|
||||
>
|
||||
<view v-if="!$slots.head" class="u-flex u-row-between">
|
||||
<view class="u-card__head--left u-flex u-line-1" v-if="title">
|
||||
<image
|
||||
:src="thumb"
|
||||
class="u-card__head--left__thumb"
|
||||
mode="aspectfull"
|
||||
v-if="thumb"
|
||||
:style="{
|
||||
height: thumbWidth + 'rpx',
|
||||
width: thumbWidth + 'rpx',
|
||||
borderRadius: thumbCircle ? '100rpx' : '6rpx'
|
||||
}"
|
||||
></image>
|
||||
<text
|
||||
class="u-card__head--left__title u-line-1"
|
||||
:style="{
|
||||
fontSize: titleSize + 'rpx',
|
||||
color: titleColor
|
||||
}"
|
||||
>
|
||||
{{ title }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="u-card__head--right u-line-1" v-if="subTitle">
|
||||
<text
|
||||
class="u-card__head__title__text"
|
||||
:style="{
|
||||
fontSize: subTitleSize + 'rpx',
|
||||
color: subTitleColor
|
||||
}"
|
||||
>
|
||||
{{ subTitle }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<slot name="head" v-else />
|
||||
</view>
|
||||
<view @tap="bodyClick" class="u-card__body" :style="[{padding: padding + 'rpx'}, bodyStyle]"><slot name="body" /></view>
|
||||
<view
|
||||
v-if="showFoot"
|
||||
class="u-card__foot"
|
||||
@tap="footClick"
|
||||
:style="[{padding: $slots.foot ? padding + 'rpx' : 0}, footStyle]"
|
||||
:class="{
|
||||
'u-border-top': footBorderTop
|
||||
}"
|
||||
>
|
||||
<slot name="foot" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* card 卡片
|
||||
* @description 卡片组件一般用于多个列表条目,且风格统一的场景
|
||||
* @tutorial https://www.uviewui.com/components/card.html
|
||||
* @property {Boolean} full 卡片与屏幕两侧是否留空隙(默认false)
|
||||
* @property {String} title 头部左边的标题
|
||||
* @property {String} title-color 标题颜色(默认#303133)
|
||||
* @property {String | Number} title-size 标题字体大小,单位rpx(默认30)
|
||||
* @property {String} sub-title 头部右边的副标题
|
||||
* @property {String} sub-title-color 副标题颜色(默认#909399)
|
||||
* @property {String | Number} sub-title-size 副标题字体大小(默认26)
|
||||
* @property {Boolean} border 是否显示边框(默认true)
|
||||
* @property {String | Number} index 用于标识点击了第几个卡片
|
||||
* @property {String} box-shadow 卡片外围阴影,字符串形式(默认none)
|
||||
* @property {String} margin 卡片与屏幕两边和上下元素的间距,需带单位,如"30rpx 20rpx"(默认30rpx)
|
||||
* @property {String | Number} border-radius 卡片整体的圆角值,单位rpx(默认16)
|
||||
* @property {Object} head-style 头部自定义样式,对象形式
|
||||
* @property {Object} body-style 中部自定义样式,对象形式
|
||||
* @property {Object} foot-style 底部自定义样式,对象形式
|
||||
* @property {Boolean} head-border-bottom 是否显示头部的下边框(默认true)
|
||||
* @property {Boolean} foot-border-top 是否显示底部的上边框(默认true)
|
||||
* @property {Boolean} show-head 是否显示头部(默认true)
|
||||
* @property {Boolean} show-head 是否显示尾部(默认true)
|
||||
* @property {String} thumb 缩略图路径,如设置将显示在标题的左边,不建议使用相对路径
|
||||
* @property {String | Number} thumb-width 缩略图的宽度,高等于宽,单位rpx(默认60)
|
||||
* @property {Boolean} thumb-circle 缩略图是否为圆形(默认false)
|
||||
* @event {Function} click 整个卡片任意位置被点击时触发
|
||||
* @event {Function} head-click 卡片头部被点击时触发
|
||||
* @event {Function} body-click 卡片主体部分被点击时触发
|
||||
* @event {Function} foot-click 卡片底部部分被点击时触发
|
||||
* @example <u-card padding="30" title="card"></u-card>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-card',
|
||||
props: {
|
||||
// 与屏幕两侧是否留空隙
|
||||
full: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题颜色
|
||||
titleColor: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 标题字体大小,单位rpx
|
||||
titleSize: {
|
||||
type: [Number, String],
|
||||
default: '30'
|
||||
},
|
||||
// 副标题
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 副标题颜色
|
||||
subTitleColor: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 副标题字体大小,单位rpx
|
||||
subTitleSize: {
|
||||
type: [Number, String],
|
||||
default: '26'
|
||||
},
|
||||
// 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时)
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 用于标识点击了第几个
|
||||
index: {
|
||||
type: [Number, String, Object],
|
||||
default: ''
|
||||
},
|
||||
// 用于隔开上下左右的边距,带单位的写法,如:"30rpx 30rpx","20rpx 20rpx 30rpx 30rpx"
|
||||
margin: {
|
||||
type: String,
|
||||
default: '30rpx'
|
||||
},
|
||||
// card卡片的圆角
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: '16'
|
||||
},
|
||||
// 头部自定义样式,对象形式
|
||||
headStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 主体自定义样式,对象形式
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 底部自定义样式,对象形式
|
||||
footStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 头部是否下边框
|
||||
headBorderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部是否有上边框
|
||||
footBorderTop: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 标题左边的缩略图
|
||||
thumb: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 缩略图宽高,单位rpx
|
||||
thumbWidth: {
|
||||
type: [String, Number],
|
||||
default: '60'
|
||||
},
|
||||
// 缩略图是否为圆形
|
||||
thumbCircle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 给head,body,foot的内边距
|
||||
padding: {
|
||||
type: [String, Number],
|
||||
default: '30'
|
||||
},
|
||||
// 是否显示头部
|
||||
showHead: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示尾部
|
||||
showFoot: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 卡片外围阴影,字符串形式
|
||||
boxShadow: {
|
||||
type: String,
|
||||
default: 'none'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click', this.index);
|
||||
},
|
||||
headClick() {
|
||||
this.$emit('head-click', this.index);
|
||||
},
|
||||
bodyClick() {
|
||||
this.$emit('body-click', this.index);
|
||||
},
|
||||
footClick() {
|
||||
this.$emit('foot-click', this.index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
|
||||
&-full {
|
||||
// 如果是与屏幕之间不留空隙,应该设置左右边距为0
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--border:after {
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
&__head {
|
||||
&--left {
|
||||
color: $u-main-color;
|
||||
|
||||
&__thumb {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
&__title {
|
||||
max-width: 400rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&--right {
|
||||
color: $u-tips-color;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
&__foot {
|
||||
color: $u-tips-color;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<view class="u-cell-box">
|
||||
<view class="u-cell-title" v-if="title" :style="[titleStyle]">
|
||||
{{title}}
|
||||
</view>
|
||||
<view class="u-cell-item-box" :class="{'u-border-bottom u-border-top': border}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* cellGroup 单元格父组件Group
|
||||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-item
|
||||
* @tutorial https://www.uviewui.com/components/cell.html
|
||||
* @property {String} title 分组标题
|
||||
* @property {Boolean} border 是否显示外边框(默认true)
|
||||
* @property {Object} title-style 分组标题的的样式,对象形式,如{'font-size': '24rpx'} 或 {'fontSize': '24rpx'}
|
||||
* @example <u-cell-group title="设置喜好">
|
||||
*/
|
||||
export default {
|
||||
name: "u-cell-group",
|
||||
props: {
|
||||
// 分组标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示分组list上下边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 分组标题的样式,对象形式,注意驼峰属性写法
|
||||
// 类似 {'font-size': '24rpx'} 和 {'fontSize': '24rpx'}
|
||||
titleStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
index: 0,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-cell-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.u-cell-title {
|
||||
padding: 30rpx 32rpx 10rpx 32rpx;
|
||||
font-size: 30rpx;
|
||||
text-align: left;
|
||||
color: $u-tips-color;
|
||||
}
|
||||
|
||||
.u-cell-item-box {
|
||||
background-color: #FFFFFF;
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,316 @@
|
|||
<template>
|
||||
<view
|
||||
@tap="click"
|
||||
class="u-cell"
|
||||
:class="{ 'u-border-bottom': borderBottom, 'u-border-top': borderTop, 'u-col-center': center, 'u-cell--required': required }"
|
||||
hover-stay-time="150"
|
||||
:hover-class="hoverClass"
|
||||
:style="{
|
||||
backgroundColor: bgColor
|
||||
}"
|
||||
>
|
||||
<u-icon :size="iconSize" :name="icon" v-if="icon" :custom-style="iconStyle" class="u-cell__left-icon-wrap"></u-icon>
|
||||
<view class="u-flex" v-else>
|
||||
<slot name="icon"></slot>
|
||||
</view>
|
||||
<view
|
||||
class="u-cell_title"
|
||||
:style="[
|
||||
{
|
||||
width: titleWidth ? titleWidth + 'rpx' : 'auto'
|
||||
},
|
||||
titleStyle
|
||||
]"
|
||||
>
|
||||
<block v-if="title !== ''">{{ title }}</block>
|
||||
<slot name="title" v-else></slot>
|
||||
|
||||
<view class="u-cell__label" v-if="label || $slots.label" :style="[labelStyle]">
|
||||
<block v-if="label !== ''">{{ label }}</block>
|
||||
<slot name="label" v-else></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="u-cell__value" :style="[valueStyle]">
|
||||
<block class="u-cell__value" v-if="value !== ''">{{ value }}</block>
|
||||
<slot v-else></slot>
|
||||
</view>
|
||||
<view class="u-flex u-cell_right" v-if="$slots['right-icon']">
|
||||
<slot name="right-icon"></slot>
|
||||
</view>
|
||||
<u-icon v-if="arrow" name="arrow-right" :style="[arrowStyle]" class="u-icon-wrap u-cell__right-icon-wrap"></u-icon>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* cellItem 单元格Item
|
||||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-group使用
|
||||
* @tutorial https://www.uviewui.com/components/cell.html
|
||||
* @property {String} title 左侧标题
|
||||
* @property {String} icon 左侧图标名,只支持uView内置图标,见Icon 图标
|
||||
* @property {Object} icon-style 左边图标的样式,对象形式
|
||||
* @property {String} value 右侧内容
|
||||
* @property {String} label 标题下方的描述信息
|
||||
* @property {Boolean} border-bottom 是否显示cell的下边框(默认true)
|
||||
* @property {Boolean} border-top 是否显示cell的上边框(默认false)
|
||||
* @property {Boolean} center 是否使内容垂直居中(默认false)
|
||||
* @property {String} hover-class 是否开启点击反馈,none为无效果(默认true)
|
||||
* // @property {Boolean} border-gap border-bottom为true时,Cell列表中间的条目的下边框是否与左边有一个间隔(默认true)
|
||||
* @property {Boolean} arrow 是否显示右侧箭头(默认true)
|
||||
* @property {Boolean} required 箭头方向,可选值(默认right)
|
||||
* @property {Boolean} arrow-direction 是否显示左边表示必填的星号(默认false)
|
||||
* @property {Object} title-style 标题样式,对象形式
|
||||
* @property {Object} value-style 右侧内容样式,对象形式
|
||||
* @property {Object} label-style 标题下方描述信息的样式,对象形式
|
||||
* @property {String} bg-color 背景颜色(默认transparent)
|
||||
* @property {String Number} index 用于在click事件回调中返回,标识当前是第几个Item
|
||||
* @property {String Number} title-width 标题的宽度,单位rpx
|
||||
* @example <u-cell-item icon="integral-fill" title="会员等级" value="新版本"></u-cell-item>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-cell-item',
|
||||
props: {
|
||||
// 左侧图标名称(只能uView内置图标),或者图标src
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 左侧标题
|
||||
title: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 右侧内容
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 标题下方的描述信息
|
||||
label: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 是否显示下边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示上边框
|
||||
borderTop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 多个cell中,中间的cell显示下划线时,下划线是否给一个到左边的距离
|
||||
// 1.4.0版本废除此参数,默认边框由border-top和border-bottom提供,此参数会造成干扰
|
||||
// borderGap: {
|
||||
// type: Boolean,
|
||||
// default: true
|
||||
// },
|
||||
// 是否开启点击反馈,即点击时cell背景为灰色,none为无效果
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'u-cell-hover'
|
||||
},
|
||||
// 是否显示右侧箭头
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 内容是否垂直居中
|
||||
center: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示左边表示必填的星号
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题的宽度,单位rpx
|
||||
titleWidth: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 右侧箭头方向,可选值:right|up|down,默认为right
|
||||
arrowDirection: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// 控制标题的样式
|
||||
titleStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 右侧显示内容的样式
|
||||
valueStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 描述信息的样式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
// 用于识别被点击的是第几个cell
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 是否使用lable插槽
|
||||
useLabelSlot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 左边图标的大小,单位rpx,只对传入icon字段时有效
|
||||
iconSize: {
|
||||
type: [Number, String],
|
||||
default: 34
|
||||
},
|
||||
// 左边图标的样式,对象形式
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
arrowStyle() {
|
||||
let style = {};
|
||||
if (this.arrowDirection == 'up') style.transform = 'rotate(-90deg)';
|
||||
else if (this.arrowDirection == 'down') style.transform = 'rotate(90deg)';
|
||||
else style.transform = 'rotate(0deg)';
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click', this.index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
.u-cell {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
width: 100%;
|
||||
padding: 26rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 54rpx;
|
||||
color: $u-content-color;
|
||||
background-color: #fff;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.u-cell_title {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.u-cell__left-icon-wrap {
|
||||
margin-right: 10rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.u-cell__right-icon-wrap {
|
||||
margin-left: 10rpx;
|
||||
color: #969799;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.u-cell__left-icon-wrap,
|
||||
.u-cell__right-icon-wrap {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
.u-cell-border:after {
|
||||
position: absolute;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
content: ' ';
|
||||
pointer-events: none;
|
||||
border-bottom: 1px solid $u-border-color;
|
||||
/* #endif */
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.u-cell-border {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.u-cell__label {
|
||||
margin-top: 6rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 36rpx;
|
||||
color: $u-tips-color;
|
||||
/* #ifndef APP-NVUE */
|
||||
word-wrap: break-word;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.u-cell__value {
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
/* #ifndef APP-NVUE */
|
||||
vertical-align: middle;
|
||||
/* #endif */
|
||||
color: $u-tips-color;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.u-cell__title,
|
||||
.u-cell__value {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.u-cell--required {
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: visible;
|
||||
/* #endif */
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-cell--required:before {
|
||||
position: absolute;
|
||||
/* #ifndef APP-NVUE */
|
||||
content: '*';
|
||||
/* #endif */
|
||||
left: 8px;
|
||||
margin-top: 4rpx;
|
||||
font-size: 14px;
|
||||
color: $u-type-error;
|
||||
}
|
||||
|
||||
.u-cell_right {
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
123
node_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue
generated
vendored
100644
123
node_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue
generated
vendored
100644
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<view class="u-checkbox-group u-clearfix">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/util/emitter.js';
|
||||
/**
|
||||
* checkboxGroup 开关选择器父组件Group
|
||||
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
|
||||
* @tutorial https://www.uviewui.com/components/checkbox.html
|
||||
* @property {String Number} max 最多能选中多少个checkbox(默认999)
|
||||
* @property {String Number} size 组件整体的大小,单位rpx(默认40)
|
||||
* @property {Boolean} disabled 是否禁用所有checkbox(默认false)
|
||||
* @property {String Number} icon-size 图标大小,单位rpx(默认20)
|
||||
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox(默认false)
|
||||
* @property {String} width 宽度,需带单位
|
||||
* @property {String} width 宽度,需带单位
|
||||
* @property {String} shape 外观形状,shape-方形,circle-圆形(默认circle)
|
||||
* @property {Boolean} wrap 是否每个checkbox都换行(默认false)
|
||||
* @property {String} active-color 选中时的颜色,应用到所有子Checkbox组件(默认#2979ff)
|
||||
* @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
|
||||
* @example <u-checkbox-group></u-checkbox-group>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-checkbox-group',
|
||||
mixins: [Emitter],
|
||||
props: {
|
||||
// 最多能选中多少个checkbox
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: 999
|
||||
},
|
||||
// 所有选中项的 name
|
||||
// value: {
|
||||
// default: Array,
|
||||
// default() {
|
||||
// return []
|
||||
// }
|
||||
// },
|
||||
// 是否禁用所有复选框
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 在表单内提交时的标识符
|
||||
name: {
|
||||
type: [Boolean, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁止点击提示语选中复选框
|
||||
labelDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 形状,square为方形,circle为原型
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
// 选中状态下的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 组件的整体大小
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 34
|
||||
},
|
||||
// 每个checkbox占u-checkbox-group的宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否每个checkbox都换行
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图标的大小,单位rpx
|
||||
iconSize: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
|
||||
this.children = [];
|
||||
},
|
||||
methods: {
|
||||
emitEvent() {
|
||||
let values = [];
|
||||
this.children.map(val => {
|
||||
if(val.value) values.push(val.name);
|
||||
})
|
||||
this.$emit('change', values);
|
||||
// 发出事件,用于在表单组件中嵌入checkbox的情况,进行验证
|
||||
// 由于头条小程序执行迟钝,故需要用几十毫秒的延时
|
||||
setTimeout(() => {
|
||||
// 将当前的值发送到 u-form-item 进行校验
|
||||
this.dispatch('u-form-item', 'on-form-change', values);
|
||||
}, 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-checkbox-group {
|
||||
/* #ifndef MP || APP-NVUE */
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,284 @@
|
|||
<template>
|
||||
<view class="u-checkbox" :style="[checkboxStyle]">
|
||||
<view class="u-checkbox__icon-wrap" @tap="toggle" :class="[iconClass]" :style="[iconStyle]">
|
||||
<u-icon class="u-checkbox__icon-wrap__icon" name="checkbox-mark" :size="checkboxIconSize" :color="iconColor"/>
|
||||
</view>
|
||||
<view class="u-checkbox__label" @tap="onClickLabel" :style="{
|
||||
fontSize: $u.addUnit(labelSize)
|
||||
}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* checkbox 复选框
|
||||
* @description 该组件需要搭配checkboxGroup组件使用,以便用户进行操作时,获得当前复选框组的选中情况。
|
||||
* @tutorial https://www.uviewui.com/components/checkbox.html
|
||||
* @property {String Number} icon-size 图标大小,单位rpx(默认20)
|
||||
* @property {String Number} label-size label字体大小,单位rpx(默认28)
|
||||
* @property {String Number} name checkbox组件的标示符
|
||||
* @property {String} shape 形状,见官网说明(默认circle)
|
||||
* @property {Boolean} disabled 是否禁用
|
||||
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox
|
||||
* @property {String} active-color 选中时的颜色,如设置CheckboxGroup的active-color将失效
|
||||
* @event {Function} change 某个checkbox状态发生变化时触发,回调为一个对象
|
||||
* @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox>
|
||||
*/
|
||||
export default {
|
||||
name: "u-checkbox",
|
||||
props: {
|
||||
// checkbox的名称
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 形状,square为方形,circle为原型
|
||||
shape: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否为选中状态
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁止点击提示语选中复选框
|
||||
labelDisabled: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
// 选中状态下的颜色,如设置此值,将会覆盖checkboxGroup的activeColor值
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标的大小,单位rpx
|
||||
iconSize: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// label的字体大小,rpx单位
|
||||
labelSize: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 组件的整体大小
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
parentDisabled: false,
|
||||
newParams: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
|
||||
this.parent = this.$u.$parent.call(this, 'u-checkbox-group');
|
||||
// 如果存在u-checkbox-group,将本组件的this塞进父组件的children中
|
||||
this.parent && this.parent.children.push(this);
|
||||
},
|
||||
computed: {
|
||||
// 是否禁用,如果父组件u-checkbox-group禁用的话,将会忽略子组件的配置
|
||||
isDisabled() {
|
||||
return this.disabled !== '' ? this.disabled : this.parent ? this.parent.disabled : false;
|
||||
},
|
||||
// 是否禁用label点击
|
||||
isLabelDisabled() {
|
||||
return this.labelDisabled !== '' ? this.labelDisabled : this.parent ? this.parent.labelDisabled : false;
|
||||
},
|
||||
// 组件尺寸,对应size的值,默认值为34rpx
|
||||
checkboxSize() {
|
||||
return this.size ? this.size : (this.parent ? this.parent.size : 34);
|
||||
},
|
||||
// 组件的勾选图标的尺寸,默认20
|
||||
checkboxIconSize() {
|
||||
return this.iconSize ? this.iconSize : (this.parent ? this.parent.iconSize : 20);
|
||||
},
|
||||
// 组件选中激活时的颜色
|
||||
elActiveColor() {
|
||||
return this.activeColor ? this.activeColor : (this.parent ? this.parent.activeColor : 'primary');
|
||||
},
|
||||
// 组件的形状
|
||||
elShape() {
|
||||
return this.shape ? this.shape : (this.parent ? this.parent.shape : 'square');
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {};
|
||||
// 既要判断是否手动禁用,还要判断用户v-model绑定的值,如果绑定为false,那么也无法选中
|
||||
if (this.elActiveColor && this.value && !this.isDisabled) {
|
||||
style.borderColor = this.elActiveColor;
|
||||
style.backgroundColor = this.elActiveColor;
|
||||
}
|
||||
style.width = this.$u.addUnit(this.checkboxSize);
|
||||
style.height = this.$u.addUnit(this.checkboxSize);
|
||||
return style;
|
||||
},
|
||||
// checkbox内部的勾选图标,如果选中状态,为白色,否则为透明色即可
|
||||
iconColor() {
|
||||
return this.value ? '#ffffff' : 'transparent';
|
||||
},
|
||||
iconClass() {
|
||||
let classes = [];
|
||||
classes.push('u-checkbox__icon-wrap--' + this.elShape);
|
||||
if (this.value == true) classes.push('u-checkbox__icon-wrap--checked');
|
||||
if (this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled');
|
||||
if (this.value && this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled--checked');
|
||||
// 支付宝小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
|
||||
return classes.join(' ');
|
||||
},
|
||||
checkboxStyle() {
|
||||
let style = {};
|
||||
if(this.parent && this.parent.width) {
|
||||
style.width = this.parent.width;
|
||||
// #ifdef MP
|
||||
// 各家小程序因为它们特殊的编译结构,使用float布局
|
||||
style.float = 'left';
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局
|
||||
style.flex = `0 0 ${this.parent.width}`;
|
||||
// #endif
|
||||
}
|
||||
if(this.parent && this.parent.wrap) {
|
||||
style.width = '100%';
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局,将宽度设置100%,即可自动换行
|
||||
style.flex = '0 0 100%';
|
||||
// #endif
|
||||
}
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClickLabel() {
|
||||
if (!this.isLabelDisabled && !this.isDisabled) {
|
||||
this.setValue();
|
||||
}
|
||||
},
|
||||
toggle() {
|
||||
if (!this.isDisabled) {
|
||||
this.setValue();
|
||||
}
|
||||
},
|
||||
emitEvent() {
|
||||
this.$emit('change', {
|
||||
value: !this.value,
|
||||
name: this.name
|
||||
})
|
||||
// 执行父组件u-checkbox-group的事件方法
|
||||
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
|
||||
setTimeout(() => {
|
||||
if(this.parent && this.parent.emitEvent) this.parent.emitEvent();
|
||||
}, 80);
|
||||
},
|
||||
// 设置input的值,这里通过input事件,设置通过v-model绑定的组件的值
|
||||
setValue() {
|
||||
// 判断是否超过了可选的最大数量
|
||||
let checkedNum = 0;
|
||||
if(this.parent && this.parent.children) {
|
||||
// 只要父组件的某一个子元素的value为true,就加1(已有的选中数量)
|
||||
this.parent.children.map(val => {
|
||||
if (val.value) checkedNum++;
|
||||
})
|
||||
}
|
||||
// 如果原来为选中状态,那么可以取消
|
||||
if (this.value == true) {
|
||||
this.emitEvent();
|
||||
this.$emit('input', !this.value);
|
||||
} else {
|
||||
// 如果超出最多可选项,提示
|
||||
if(this.parent && checkedNum >= this.parent.max) {
|
||||
return this.$u.toast(`最多可选${this.parent.max}项`);
|
||||
}
|
||||
// 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中
|
||||
this.emitEvent();
|
||||
this.$emit('input', !this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-checkbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
line-height: 1.8;
|
||||
|
||||
&__icon-wrap {
|
||||
color: $u-content-color;
|
||||
flex: none;
|
||||
display: -webkit-flex;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
color: transparent;
|
||||
text-align: center;
|
||||
transition-property: color, border-color, background-color;
|
||||
font-size: 20px;
|
||||
border: 1px solid #c8c9cc;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
|
||||
&__icon {
|
||||
line-height: 0;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
&--circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
&--checked {
|
||||
color: #fff;
|
||||
background-color: $u-type-primary;
|
||||
border-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: #ebedf0;
|
||||
border-color: #c8c9cc;
|
||||
}
|
||||
|
||||
&--disabled--checked {
|
||||
color: #c8c9cc !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
word-wrap: break-word;
|
||||
margin-left: 10rpx;
|
||||
margin-right: 24rpx;
|
||||
color: $u-content-color;
|
||||
font-size: 30rpx;
|
||||
|
||||
&--disabled {
|
||||
color: #c8c9cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
220
node_modules/uview-ui/components/u-circle-progress/u-circle-progress.vue
generated
vendored
100644
220
node_modules/uview-ui/components/u-circle-progress/u-circle-progress.vue
generated
vendored
100644
|
@ -0,0 +1,220 @@
|
|||
<template>
|
||||
<view
|
||||
class="u-circle-progress"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px',
|
||||
backgroundColor: bgColor
|
||||
}"
|
||||
>
|
||||
<!-- 支付宝小程序不支持canvas-id属性,必须用id属性 -->
|
||||
<canvas
|
||||
class="u-canvas-bg"
|
||||
:canvas-id="elBgId"
|
||||
:id="elBgId"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
></canvas>
|
||||
<canvas
|
||||
class="u-canvas"
|
||||
:canvas-id="elId"
|
||||
:id="elId"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
></canvas>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* circleProgress 环形进度条
|
||||
* @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度条。注意:此组件的percent值只能动态增加,不能动态减少。
|
||||
* @tutorial https://www.uviewui.com/components/circleProgress.html
|
||||
* @property {String Number} percent 圆环进度百分比值,为数值类型,0-100
|
||||
* @property {String} inactive-color 圆环的底色,默认为灰色(该值无法动态变更)(默认#ececec)
|
||||
* @property {String} active-color 圆环激活部分的颜色(该值无法动态变更)(默认#19be6b)
|
||||
* @property {String Number} width 整个圆环组件的宽度,高度默认等于宽度值,单位rpx(默认200)
|
||||
* @property {String Number} border-width 圆环的边框宽度,单位rpx(默认14)
|
||||
* @property {String Number} duration 整个圆环执行一圈的时间,单位ms(默认呢1500)
|
||||
* @property {String} type 如设置,active-color值将会失效
|
||||
* @property {String} bg-color 整个组件背景颜色,默认为白色
|
||||
* @example <u-circle-progress active-color="#2979ff" :percent="80"></u-circle-progress>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-circle-progress',
|
||||
props: {
|
||||
// 圆环进度百分比值
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
// 限制值在0到100之间
|
||||
validator: val => {
|
||||
return val >= 0 && val <= 100;
|
||||
}
|
||||
},
|
||||
// 底部圆环的颜色(灰色的圆环)
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#ececec'
|
||||
},
|
||||
// 圆环激活部分的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#19be6b'
|
||||
},
|
||||
// 圆环线条的宽度,单位rpx
|
||||
borderWidth: {
|
||||
type: [Number, String],
|
||||
default: 14
|
||||
},
|
||||
// 整个圆形的宽度,单位rpx
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 200
|
||||
},
|
||||
// 整个圆环执行一圈的时间,单位ms
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 1500
|
||||
},
|
||||
// 主题类型
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 整个圆环进度区域的背景色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// #ifdef MP-WEIXIN
|
||||
elBgId: 'uCircleProgressBgId', // 微信小程序中不能使用this.$u.guid()形式动态生成id值,否则会报错
|
||||
elId: 'uCircleProgressElId',
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
elBgId: this.$u.guid(), // 非微信端的时候,需用动态的id,否则一个页面多个圆形进度条组件数据会混乱
|
||||
elId: this.$u.guid(),
|
||||
// #endif
|
||||
widthPx: uni.upx2px(this.width), // 转成px后的整个组件的背景宽度
|
||||
borderWidthPx: uni.upx2px(this.borderWidth), // 转成px后的圆环的宽度
|
||||
startAngle: -Math.PI / 2, // canvas画圆的起始角度,默认为3点钟方向,定位到12点钟方向
|
||||
progressContext: null, // 活动圆的canvas上下文
|
||||
newPercent: 0, // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用
|
||||
oldPercent: 0 // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
percent(nVal, oVal = 0) {
|
||||
if (nVal > 100) nVal = 100;
|
||||
if (nVal < 0) oVal = 0;
|
||||
// 此值其实等于this.percent,命名一个新
|
||||
this.newPercent = nVal;
|
||||
this.oldPercent = oVal;
|
||||
setTimeout(() => {
|
||||
// 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值
|
||||
// 将此值减少或者新增到新的百分比值
|
||||
this.drawCircleByProgress(oVal);
|
||||
}, 50);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 赋值,用于加载后第一个画圆使用
|
||||
this.newPercent = this.percent;
|
||||
this.oldPercent = 0;
|
||||
},
|
||||
computed: {
|
||||
// 有type主题时,优先起作用
|
||||
circleColor() {
|
||||
if (['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) return this.$u.color[this.type];
|
||||
else return this.activeColor;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 在h5端,必须要做一点延时才起作用,this.$nextTick()无效(HX2.4.7)
|
||||
setTimeout(() => {
|
||||
this.drawProgressBg();
|
||||
this.drawCircleByProgress(this.oldPercent);
|
||||
}, 50);
|
||||
},
|
||||
methods: {
|
||||
drawProgressBg() {
|
||||
let ctx = uni.createCanvasContext(this.elBgId, this);
|
||||
ctx.setLineWidth(this.borderWidthPx); // 设置圆环宽度
|
||||
ctx.setStrokeStyle(this.inactiveColor); // 线条颜色
|
||||
ctx.beginPath(); // 开始描绘路径
|
||||
// 设置一个原点(110,110),半径为100的圆的路径到当前路径
|
||||
let radius = this.widthPx / 2;
|
||||
ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 2 * Math.PI, false);
|
||||
ctx.stroke(); // 对路径进行描绘
|
||||
ctx.draw();
|
||||
},
|
||||
drawCircleByProgress(progress) {
|
||||
// 第一次操作进度环时将上下文保存到了this.data中,直接使用即可
|
||||
let ctx = this.progressContext;
|
||||
if (!ctx) {
|
||||
ctx = uni.createCanvasContext(this.elId, this);
|
||||
this.progressContext = ctx;
|
||||
}
|
||||
// 表示进度的两端为圆形
|
||||
ctx.setLineCap('round');
|
||||
// 设置线条的宽度和颜色
|
||||
ctx.setLineWidth(this.borderWidthPx);
|
||||
ctx.setStrokeStyle(this.circleColor);
|
||||
// 将总过渡时间除以100,得出每修改百分之一进度所需的时间
|
||||
let time = Math.floor(this.duration / 100);
|
||||
// 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的
|
||||
// 3点钟方向开始画图,转为更好理解的12点钟方向开始作图,这需要起始角和终止角同时加上this.startAngle值
|
||||
let endAngle = ((2 * Math.PI) / 100) * progress + this.startAngle;
|
||||
ctx.beginPath();
|
||||
// 半径为整个canvas宽度的一半
|
||||
let radius = this.widthPx / 2;
|
||||
ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false);
|
||||
ctx.stroke();
|
||||
ctx.draw();
|
||||
// 如果变更后新值大于旧值,意味着增大了百分比
|
||||
if (this.newPercent > this.oldPercent) {
|
||||
// 每次递增百分之一
|
||||
progress++;
|
||||
// 如果新增后的值,大于需要设置的值百分比值,停止继续增加
|
||||
if (progress > this.newPercent) return;
|
||||
} else {
|
||||
// 同理于上面
|
||||
progress--;
|
||||
if (progress < this.newPercent) return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
// 定时器,每次操作间隔为time值,为了让进度条有动画效果
|
||||
this.drawCircleByProgress(progress);
|
||||
}, time);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
.u-circle-progress {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-canvas-bg {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.u-canvas {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
147
node_modules/uview-ui/components/u-circle-progress/u-line-progress/u-line-progress.vue
generated
vendored
100644
147
node_modules/uview-ui/components/u-circle-progress/u-line-progress/u-line-progress.vue
generated
vendored
100644
|
@ -0,0 +1,147 @@
|
|||
<template>
|
||||
<view class="u-progress" :style="{
|
||||
borderRadius: round ? '100rpx' : 0,
|
||||
height: height + 'rpx',
|
||||
backgroundColor: inactiveColor
|
||||
}">
|
||||
<view :class="[
|
||||
type ? `u-type-${type}-bg` : '',
|
||||
striped ? 'u-striped' : '',
|
||||
striped && stripedActive ? 'u-striped-active' : ''
|
||||
]" class="u-active" :style="[progressStyle]">
|
||||
<slot v-if="$slots.default || $slots.$default" />
|
||||
<block v-else-if="showPercent">
|
||||
{{percent + '%'}}
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* lineProgress 线型进度条
|
||||
* @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。
|
||||
* @tutorial https://www.uviewui.com/components/lineProgress.html
|
||||
* @property {String Number} percent 进度条百分比值,为数值类型,0-100
|
||||
* @property {Boolean} round 进度条两端是否为半圆(默认true)
|
||||
* @property {String} type 如设置,active-color值将会失效
|
||||
* @property {String} active-color 进度条激活部分的颜色(默认#19be6b)
|
||||
* @property {String} inactive-color 进度条的底色(默认#ececec)
|
||||
* @property {Boolean} show-percent 是否在进度条内部显示当前的百分比值数值(默认true)
|
||||
* @property {String Number} height 进度条的高度,单位rpx(默认28)
|
||||
* @property {Boolean} striped 是否显示进度条激活部分的条纹(默认false)
|
||||
* @property {Boolean} striped-active 条纹是否具有动态效果(默认false)
|
||||
* @example <u-line-progress :percent="70" :show-percent="true"></u-line-progress>
|
||||
*/
|
||||
export default {
|
||||
name: "u-line-progress",
|
||||
props: {
|
||||
// 两端是否显示半圆形
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 主题颜色
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 激活部分的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#19be6b'
|
||||
},
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#ececec'
|
||||
},
|
||||
// 进度百分比,数值
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否在进度条内部显示百分比的值
|
||||
showPercent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 进度条的高度,单位rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 28
|
||||
},
|
||||
// 是否显示条纹
|
||||
striped: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 条纹是否显示活动状态
|
||||
stripedActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
progressStyle() {
|
||||
let style = {};
|
||||
style.width = this.percent + '%';
|
||||
if(this.activeColor) style.backgroundColor = this.activeColor;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-progress {
|
||||
overflow: hidden;
|
||||
height: 15px;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.u-active {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
@include vue-flex;
|
||||
justify-items: flex-end;
|
||||
justify-content: space-around;
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.u-striped {
|
||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||
background-size: 39px 39px;
|
||||
}
|
||||
|
||||
.u-striped-active {
|
||||
animation: progress-stripes 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes progress-stripes {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 39px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,156 @@
|
|||
<template>
|
||||
<view class="u-col" :class="[
|
||||
'u-col-' + span
|
||||
]" :style="{
|
||||
padding: `0 ${Number(gutter)/2 + 'rpx'}`,
|
||||
marginLeft: 100 / 12 * offset + '%',
|
||||
flex: `0 0 ${100 / 12 * span}%`,
|
||||
alignItems: uAlignItem,
|
||||
justifyContent: uJustify,
|
||||
textAlign: textAlign
|
||||
}"
|
||||
@tap="click">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* col 布局单元格
|
||||
* @description 通过基础的 12 分栏,迅速简便地创建布局(搭配<u-row>使用)
|
||||
* @tutorial https://www.uviewui.com/components/layout.html
|
||||
* @property {String Number} span 栅格占据的列数,总12等分(默认0)
|
||||
* @property {String} text-align 文字水平对齐方式(默认left)
|
||||
* @property {String Number} offset 分栏左边偏移,计算方式与span相同(默认0)
|
||||
* @example <u-col span="3"><view class="demo-layout bg-purple"></view></u-col>
|
||||
*/
|
||||
export default {
|
||||
name: "u-col",
|
||||
props: {
|
||||
// 占父容器宽度的多少等分,总分为12份
|
||||
span: {
|
||||
type: [Number, String],
|
||||
default: 12
|
||||
},
|
||||
// 指定栅格左侧的间隔数(总12栏)
|
||||
offset: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
|
||||
justify: {
|
||||
type: String,
|
||||
default: 'start'
|
||||
},
|
||||
// 垂直对齐方式,可选值为top、center、bottom
|
||||
align: {
|
||||
type: String,
|
||||
default: 'center'
|
||||
},
|
||||
// 文字对齐方式
|
||||
textAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 是否阻止事件传播
|
||||
stop: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
gutter: 20, // 给col添加间距,左右边距各占一半,从父组件u-row获取
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false;
|
||||
},
|
||||
mounted() {
|
||||
// 获取父组件实例,并赋值给对应的参数
|
||||
this.parent = this.$u.$parent.call(this, 'u-row');
|
||||
if (this.parent) {
|
||||
this.gutter = this.parent.gutter;
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(e) {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-col {
|
||||
/* #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO */
|
||||
float: left;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,204 @@
|
|||
<template>
|
||||
<view class="u-collapse-item" :style="[itemStyle]">
|
||||
<view :hover-stay-time="200" class="u-collapse-head" @tap.stop="headClick" :hover-class="hoverClass" :style="[headStyle]">
|
||||
<block v-if="!$slots['title-all']">
|
||||
<view v-if="!$slots['title']" class="u-collapse-title u-line-1" :style="[{ textAlign: align ? align : 'left' },
|
||||
isShow && activeStyle && !arrow ? activeStyle : '']">
|
||||
{{ title }}
|
||||
</view>
|
||||
<slot v-else name="title" />
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon v-if="arrow" :color="arrowColor" :class="{ 'u-arrow-down-icon-active': isShow }"
|
||||
class="u-arrow-down-icon" name="arrow-down"></u-icon>
|
||||
</view>
|
||||
</block>
|
||||
<slot v-else name="title-all" />
|
||||
</view>
|
||||
<view class="u-collapse-body" :style="[{
|
||||
height: isShow ? height + 'px' : '0'
|
||||
}]">
|
||||
<view class="u-collapse-content" :id="elId" :style="[bodyStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* collapseItem 手风琴Item
|
||||
* @description 通过折叠面板收纳内容区域(搭配u-collapse使用)
|
||||
* @tutorial https://www.uviewui.com/components/collapse.html
|
||||
* @property {String} title 面板标题
|
||||
* @property {String Number} index 主要用于事件的回调,标识那个Item被点击
|
||||
* @property {Boolean} disabled 面板是否可以打开或收起(默认false)
|
||||
* @property {Boolean} open 设置某个面板的初始状态是否打开(默认false)
|
||||
* @property {String Number} name 唯一标识符,如不设置,默认用当前collapse-item的索引值
|
||||
* @property {String} align 标题的对齐方式(默认left)
|
||||
* @property {Object} active-style 不显示箭头时,可以添加当前选择的collapse-item活动样式,对象形式
|
||||
* @event {Function} change 某个item被打开或者收起时触发
|
||||
* @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item>
|
||||
*/
|
||||
export default {
|
||||
name: "u-collapse-item",
|
||||
props: {
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题的对齐方式
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 是否可以点击收起
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// collapse显示与否
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 唯一标识符
|
||||
name: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
//活动样式
|
||||
activeStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 标识当前为第几个
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
elId: this.$u.guid(),
|
||||
height: 0, // body内容的高度
|
||||
headStyle: {}, // 头部样式,对象形式
|
||||
bodyStyle: {}, // 主体部分样式
|
||||
itemStyle: {}, // 每个item的整体样式
|
||||
arrowColor: '', // 箭头的颜色
|
||||
hoverClass: '', // 头部按下时的效果样式类
|
||||
arrow: true, // 是否显示右侧箭头
|
||||
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
open(val) {
|
||||
this.isShow = val;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false;
|
||||
// 获取u-collapse的信息,放在u-collapse是为了方便,不用每个u-collapse-item写一遍
|
||||
this.isShow = this.open;
|
||||
},
|
||||
methods: {
|
||||
// 异步获取内容,或者动态修改了内容时,需要重新初始化
|
||||
init() {
|
||||
this.parent = this.$u.$parent.call(this, 'u-collapse');
|
||||
if(this.parent) {
|
||||
this.nameSync = this.name ? this.name : this.parent.childrens.length;
|
||||
this.parent.childrens.push(this);
|
||||
this.headStyle = this.parent.headStyle;
|
||||
this.bodyStyle = this.parent.bodyStyle;
|
||||
this.arrowColor = this.parent.arrowColor;
|
||||
this.hoverClass = this.parent.hoverClass;
|
||||
this.arrow = this.parent.arrow;
|
||||
this.itemStyle = this.parent.itemStyle;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.queryRect();
|
||||
});
|
||||
},
|
||||
// 点击collapsehead头部
|
||||
headClick() {
|
||||
if (this.disabled) return;
|
||||
if (this.parent && this.parent.accordion == true) {
|
||||
this.parent.childrens.map(val => {
|
||||
// 自身不设置为false,因为后面有this.isShow = !this.isShow;处理了
|
||||
if (this != val) {
|
||||
val.isShow = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.isShow = !this.isShow;
|
||||
// 触发本组件的事件
|
||||
this.$emit('change', {
|
||||
index: this.index,
|
||||
show: this.isShow
|
||||
})
|
||||
// 只有在打开时才发出事件
|
||||
if (this.isShow) this.parent && this.parent.onChange();
|
||||
this.$forceUpdate();
|
||||
},
|
||||
// 查询内容高度
|
||||
queryRect() {
|
||||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
|
||||
// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同
|
||||
this.$uGetRect('#' + this.elId).then(res => {
|
||||
this.height = res.height;
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-collapse-head {
|
||||
position: relative;
|
||||
@include vue-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: $u-main-color;
|
||||
font-size: 30rpx;
|
||||
line-height: 1;
|
||||
padding: 24rpx 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.u-collapse-title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-arrow-down-icon {
|
||||
transition: all 0.3s;
|
||||
margin-right: 20rpx;
|
||||
margin-left: 14rpx;
|
||||
}
|
||||
|
||||
.u-arrow-down-icon-active {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.u-collapse-body {
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.u-collapse-content {
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
color: $u-tips-color;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<view class="u-collapse">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* collapse 手风琴
|
||||
* @description 通过折叠面板收纳内容区域
|
||||
* @tutorial https://www.uviewui.com/components/collapse.html
|
||||
* @property {Boolean} accordion 是否手风琴模式(默认true)
|
||||
* @property {Boolean} arrow 是否显示标题右侧的箭头(默认true)
|
||||
* @property {String} arrow-color 标题右侧箭头的颜色(默认#909399)
|
||||
* @property {Object} head-style 标题自定义样式,对象形式
|
||||
* @property {Object} body-style 主体自定义样式,对象形式
|
||||
* @property {String} hover-class 样式类名,按下时有效(默认u-hover-class)
|
||||
* @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array)
|
||||
* @example <u-collapse></u-collapse>
|
||||
*/
|
||||
export default {
|
||||
name:"u-collapse",
|
||||
props: {
|
||||
// 是否手风琴模式
|
||||
accordion: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 头部的样式
|
||||
headStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 主体的样式
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 每一个item的样式
|
||||
itemStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示右侧的箭头
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 箭头的颜色
|
||||
arrowColor: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 标题部分按压时的样式类,"none"为无效果
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'u-hover-class'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.childrens = []
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 重新初始化一次内部的所有子元素的高度计算,用于异步获取数据渲染的情况
|
||||
init() {
|
||||
this.childrens.forEach((vm, index) => {
|
||||
vm.init();
|
||||
})
|
||||
},
|
||||
// collapse item被点击,由collapse item调用父组件方法
|
||||
onChange() {
|
||||
let activeItem = [];
|
||||
this.childrens.forEach((vm, index) => {
|
||||
if (vm.isShow) {
|
||||
activeItem.push(vm.nameSync);
|
||||
}
|
||||
})
|
||||
// 如果是手风琴模式,只有一个匹配结果,也即activeItem长度为1,将其转为字符串
|
||||
if (this.accordion) activeItem = activeItem.join('');
|
||||
this.$emit('change', activeItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
</style>
|
|
@ -0,0 +1,237 @@
|
|||
<template>
|
||||
<view
|
||||
class="u-notice-bar"
|
||||
:style="{
|
||||
background: computeBgColor,
|
||||
padding: padding
|
||||
}"
|
||||
:class="[
|
||||
type ? `u-type-${type}-light-bg` : ''
|
||||
]"
|
||||
>
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon class="u-left-icon" v-if="volumeIcon" name="volume-fill" :size="volumeSize" :color="computeColor"></u-icon>
|
||||
</view>
|
||||
<swiper :disable-touch="disableTouch" @change="change" :autoplay="autoplay && playState == 'play'" :vertical="vertical" circular :interval="duration" class="u-swiper">
|
||||
<swiper-item v-for="(item, index) in list" :key="index" class="u-swiper-item">
|
||||
<view
|
||||
class="u-news-item u-line-1"
|
||||
:style="[textStyle]"
|
||||
@tap="click(index)"
|
||||
:class="['u-type-' + type]"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon @click="getMore" class="u-right-icon" v-if="moreIcon" name="arrow-right" :size="26" :color="computeColor"></u-icon>
|
||||
<u-icon @click="close" class="u-right-icon" v-if="closeIcon" name="close" :size="24" :color="computeColor"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// 显示的内容,数组
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 显示的主题,success|error|primary|info|warning
|
||||
type: {
|
||||
type: String,
|
||||
default: 'warning'
|
||||
},
|
||||
// 是否显示左侧的音量图标
|
||||
volumeIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示右侧的右箭头图标
|
||||
moreIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示右侧的关闭图标
|
||||
closeIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 文字颜色,各图标也会使用文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 滚动方向,row-水平滚动,column-垂直滚动
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 字体大小,单位rpx
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
},
|
||||
// 滚动一个周期的时间长,单位ms
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 2000
|
||||
},
|
||||
// 音量喇叭的大小
|
||||
volumeSize: {
|
||||
type: [Number, String],
|
||||
default: 34
|
||||
},
|
||||
// 水平滚动时的滚动速度,即每秒滚动多少rpx,这有利于控制文字无论多少时,都能有一个恒定的速度
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 160
|
||||
},
|
||||
// 水平滚动时,是否采用衔接形式滚动
|
||||
isCircular: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动方向,horizontal-水平滚动,vertical-垂直滚动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
// 播放状态,play-播放,paused-暂停
|
||||
playState: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 是否禁止用手滑动切换
|
||||
// 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序
|
||||
disableTouch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 通知的边距
|
||||
padding: {
|
||||
type: [Number, String],
|
||||
default: '18rpx 24rpx'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算字体颜色,如果没有自定义的,就用uview主题颜色
|
||||
computeColor() {
|
||||
if (this.color) return this.color;
|
||||
// 如果是无主题,就默认使用content-color
|
||||
else if(this.type == 'none') return '#606266';
|
||||
else return this.type;
|
||||
},
|
||||
// 文字内容的样式
|
||||
textStyle() {
|
||||
let style = {};
|
||||
if (this.color) style.color = this.color;
|
||||
else if(this.type == 'none') style.color = '#606266';
|
||||
style.fontSize = this.fontSize + 'rpx';
|
||||
return style;
|
||||
},
|
||||
// 垂直或者水平滚动
|
||||
vertical() {
|
||||
if(this.mode == 'horizontal') return false;
|
||||
else return true;
|
||||
},
|
||||
// 计算背景颜色
|
||||
computeBgColor() {
|
||||
if (this.bgColor) return this.bgColor;
|
||||
else if(this.type == 'none') return 'transparent';
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// animation: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 点击通告栏
|
||||
click(index) {
|
||||
this.$emit('click', index);
|
||||
},
|
||||
// 点击关闭按钮
|
||||
close() {
|
||||
this.$emit('close');
|
||||
},
|
||||
// 点击更多箭头按钮
|
||||
getMore() {
|
||||
this.$emit('getMore');
|
||||
},
|
||||
change(e) {
|
||||
let index = e.detail.current;
|
||||
if(index == this.list.length - 1) {
|
||||
this.$emit('end');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-notice-bar {
|
||||
width: 100%;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
padding: 18rpx 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-swiper {
|
||||
font-size: 26rpx;
|
||||
height: 32rpx;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.u-swiper-item {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-news-item {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-right-icon {
|
||||
margin-left: 12rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-left-icon {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,318 @@
|
|||
<template>
|
||||
<view class="u-countdown">
|
||||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))">
|
||||
<view class="u-countdown-time" :style="[letterStyle]">
|
||||
{{ d }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-countdown-colon"
|
||||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
|
||||
v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))"
|
||||
>
|
||||
{{ separator == 'colon' ? ':' : '天' }}
|
||||
</view>
|
||||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showHours">
|
||||
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
|
||||
{{ h }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-countdown-colon"
|
||||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
|
||||
v-if="showHours"
|
||||
>
|
||||
{{ separator == 'colon' ? ':' : '时' }}
|
||||
</view>
|
||||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showMinutes">
|
||||
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
|
||||
{{ i }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-countdown-colon"
|
||||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
|
||||
v-if="showMinutes"
|
||||
>
|
||||
{{ separator == 'colon' ? ':' : '分' }}
|
||||
</view>
|
||||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showSeconds">
|
||||
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
|
||||
{{ s }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="u-countdown-colon"
|
||||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
|
||||
v-if="showSeconds && separator == 'zh'"
|
||||
>
|
||||
秒
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* countDown 倒计时
|
||||
* @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。
|
||||
* @tutorial https://www.uviewui.com/components/countDown.html
|
||||
* @property {String Number} timestamp 倒计时,单位为秒
|
||||
* @property {Boolean} autoplay 是否自动开始倒计时,如果为false,需手动调用开始方法。见官网说明(默认true)
|
||||
* @property {String} separator 分隔符,colon为英文冒号,zh为中文(默认colon)
|
||||
* @property {String Number} separator-size 分隔符的字体大小,单位rpx(默认30)
|
||||
* @property {String} separator-color 分隔符的颜色(默认#303133)
|
||||
* @property {String Number} font-size 倒计时字体大小,单位rpx(默认30)
|
||||
* @property {Boolean} show-border 是否显示倒计时数字的边框(默认false)
|
||||
* @property {Boolean} hide-zero-day 当"天"的部分为0时,隐藏该字段 (默认true)
|
||||
* @property {String} border-color 数字边框的颜色(默认#303133)
|
||||
* @property {String} bg-color 倒计时数字的背景颜色(默认#ffffff)
|
||||
* @property {String} color 倒计时数字的颜色(默认#303133)
|
||||
* @property {String} height 数字高度值(宽度等同此值),设置边框时看情况是否需要设置此值,单位rpx(默认auto)
|
||||
* @property {Boolean} show-days 是否显示倒计时的"天"部分(默认true)
|
||||
* @property {Boolean} show-hours 是否显示倒计时的"时"部分(默认true)
|
||||
* @property {Boolean} show-minutes 是否显示倒计时的"分"部分(默认true)
|
||||
* @property {Boolean} show-seconds 是否显示倒计时的"秒"部分(默认true)
|
||||
* @event {Function} end 倒计时结束
|
||||
* @event {Function} change 每秒触发一次,回调为当前剩余的倒计秒数
|
||||
* @example <u-count-down ref="uCountDown" :timestamp="86400" :autoplay="false"></u-count-down>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-count-down',
|
||||
props: {
|
||||
// 倒计时的时间,秒为单位
|
||||
timestamp: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 是否自动开始倒计时
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 用英文冒号(colon)或者中文(zh)当做分隔符,false的时候为中文,如:"11:22"或"11时22秒"
|
||||
separator: {
|
||||
type: String,
|
||||
default: 'colon'
|
||||
},
|
||||
// 分隔符的大小,单位rpx
|
||||
separatorSize: {
|
||||
type: [Number, String],
|
||||
default: 30
|
||||
},
|
||||
// 分隔符颜色
|
||||
separatorColor: {
|
||||
type: String,
|
||||
default: "#303133"
|
||||
},
|
||||
// 字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 字体大小,单位rpx
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 30
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
// 数字框高度,单位rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否显示数字框
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 是否显示秒
|
||||
showSeconds: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示分钟
|
||||
showMinutes: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示小时
|
||||
showHours: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示“天”
|
||||
showDays: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 当"天"的部分为0时,不显示
|
||||
hideZeroDay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听时间戳的变化
|
||||
timestamp(newVal, oldVal) {
|
||||
// 如果倒计时间发生变化,清除定时器,重新开始倒计时
|
||||
this.clearTimer();
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
d: '00', // 天的默认值
|
||||
h: '00', // 小时的默认值
|
||||
i: '00', // 分钟的默认值
|
||||
s: '00', // 秒的默认值
|
||||
timer: null ,// 定时器
|
||||
seconds: 0, // 记录不停倒计过程中变化的秒数
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 倒计时item的样式,item为分别的时分秒部分的数字
|
||||
itemStyle() {
|
||||
let style = {};
|
||||
if(this.height) {
|
||||
style.height = this.height + 'rpx';
|
||||
style.width = this.height + 'rpx';
|
||||
}
|
||||
if(this.showBorder) {
|
||||
style.borderStyle = 'solid';
|
||||
style.borderColor = this.borderColor;
|
||||
style.borderWidth = '1px';
|
||||
}
|
||||
if(this.bgColor) {
|
||||
style.backgroundColor = this.bgColor;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
// 倒计时数字的样式
|
||||
letterStyle() {
|
||||
let style = {};
|
||||
if(this.fontSize) style.fontSize = this.fontSize + 'rpx';
|
||||
if(this.color) style.color = this.color;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 如果自动倒计时
|
||||
this.autoplay && this.timestamp && this.start();
|
||||
},
|
||||
methods: {
|
||||
// 倒计时
|
||||
start() {
|
||||
// 避免可能出现的倒计时重叠情况
|
||||
this.clearTimer();
|
||||
if (this.timestamp <= 0) return;
|
||||
this.seconds = Number(this.timestamp);
|
||||
this.formatTime(this.seconds);
|
||||
this.timer = setInterval(() => {
|
||||
this.seconds--;
|
||||
// 发出change事件
|
||||
this.$emit('change', this.seconds);
|
||||
if (this.seconds < 0) {
|
||||
return this.end();
|
||||
}
|
||||
this.formatTime(this.seconds);
|
||||
}, 1000);
|
||||
},
|
||||
// 格式化时间
|
||||
formatTime(seconds) {
|
||||
// 小于等于0的话,结束倒计时
|
||||
seconds <= 0 && this.end();
|
||||
let [day, hour, minute, second] = [0, 0, 0, 0];
|
||||
day = Math.floor(seconds / (60 * 60 * 24));
|
||||
// 判断是否显示“天”参数,如果不显示,将天部分的值,加入到小时中
|
||||
// hour为给后面计算秒和分等用的(基于显示天的前提下计算)
|
||||
hour = Math.floor(seconds / (60 * 60)) - day * 24;
|
||||
// showHour为需要显示的小时
|
||||
let showHour = null;
|
||||
if(this.showDays) {
|
||||
showHour = hour;
|
||||
} else {
|
||||
// 如果不显示天数,将“天”部分的时间折算到小时中去
|
||||
showHour = Math.floor(seconds / (60 * 60));
|
||||
}
|
||||
minute = Math.floor(seconds / 60) - hour * 60 - day * 24 * 60;
|
||||
second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
|
||||
// 如果小于10,在前面补上一个"0"
|
||||
showHour = showHour < 10 ? '0' + showHour : showHour;
|
||||
minute = minute < 10 ? '0' + minute : minute;
|
||||
second = second < 10 ? '0' + second : second;
|
||||
day = day < 10 ? '0' + day : day;
|
||||
this.d = day;
|
||||
this.h = showHour;
|
||||
this.i = minute;
|
||||
this.s = second;
|
||||
},
|
||||
// 停止倒计时
|
||||
end() {
|
||||
this.clearTimer();
|
||||
this.$emit('end', {});
|
||||
},
|
||||
// 清除定时器
|
||||
clearTimer() {
|
||||
if(this.timer) {
|
||||
// 清除定时器
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-countdown {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-countdown-item {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rpx;
|
||||
border-radius: 6rpx;
|
||||
white-space: nowrap;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.u-countdown-time {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.u-countdown-colon {
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
padding: 0 5rpx;
|
||||
line-height: 1;
|
||||
align-items: center;
|
||||
padding-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.u-countdown-scale {
|
||||
transform: scale(0.9);
|
||||
transform-origin: center center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,241 @@
|
|||
<template>
|
||||
<view
|
||||
class="u-count-num"
|
||||
:style="{
|
||||
fontSize: fontSize + 'rpx',
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
color: color
|
||||
}"
|
||||
>
|
||||
{{ displayValue }}
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* countTo 数字滚动
|
||||
* @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
|
||||
* @tutorial https://www.uviewui.com/components/countTo.html
|
||||
* @property {String Number} start-val 开始值
|
||||
* @property {String Number} end-val 结束值
|
||||
* @property {String Number} duration 滚动过程所需的时间,单位ms(默认2000)
|
||||
* @property {Boolean} autoplay 是否自动开始滚动(默认true)
|
||||
* @property {String Number} decimals 要显示的小数位数,见官网说明(默认0)
|
||||
* @property {Boolean} use-easing 滚动结束时,是否缓动结尾,见官网说明(默认true)
|
||||
* @property {String} separator 千位分隔符,见官网说明
|
||||
* @property {String} color 字体颜色(默认#303133)
|
||||
* @property {String Number} font-size 字体大小,单位rpx(默认50)
|
||||
* @property {Boolean} bold 字体是否加粗(默认false)
|
||||
* @event {Function} end 数值滚动到目标值时触发
|
||||
* @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-count-to',
|
||||
props: {
|
||||
// 开始的数值,默认从0增长到某一个数
|
||||
startVal: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 要滚动的目标数值,必须
|
||||
endVal: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
required: true
|
||||
},
|
||||
// 滚动到目标数值的动画持续时间,单位为毫秒(ms)
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 2000
|
||||
},
|
||||
// 设置数值后是否自动开始滚动
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 要显示的小数位数
|
||||
decimals: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 是否在即将到达目标数值的时候,使用缓慢滚动的效果
|
||||
useEasing: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 十进制分割
|
||||
decimal: {
|
||||
type: [Number, String],
|
||||
default: '.'
|
||||
},
|
||||
// 字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#303133'
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 50
|
||||
},
|
||||
// 是否加粗字体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 千位分隔符,类似金额的分割(¥23,321.05中的",")
|
||||
separator: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localStartVal: this.startVal,
|
||||
displayValue: this.formatNumber(this.startVal),
|
||||
printVal: null,
|
||||
paused: false, // 是否暂停
|
||||
localDuration: Number(this.duration),
|
||||
startTime: null, // 开始的时间
|
||||
timestamp: null, // 时间戳
|
||||
remaining: null, // 停留的时间
|
||||
rAF: null,
|
||||
lastTime: 0 // 上一次的时间
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
countDown() {
|
||||
return this.startVal > this.endVal;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
startVal() {
|
||||
this.autoplay && this.start();
|
||||
},
|
||||
endVal() {
|
||||
this.autoplay && this.start();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.autoplay && this.start();
|
||||
},
|
||||
methods: {
|
||||
easingFn(t, b, c, d) {
|
||||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
|
||||
},
|
||||
requestAnimationFrame(callback) {
|
||||
const currTime = new Date().getTime();
|
||||
// 为了使setTimteout的尽可能的接近每秒60帧的效果
|
||||
const timeToCall = Math.max(0, 16 - (currTime - this.lastTime));
|
||||
const id = setTimeout(() => {
|
||||
callback(currTime + timeToCall);
|
||||
}, timeToCall);
|
||||
this.lastTime = currTime + timeToCall;
|
||||
return id;
|
||||
},
|
||||
|
||||
cancelAnimationFrame(id) {
|
||||
clearTimeout(id);
|
||||
},
|
||||
// 开始滚动数字
|
||||
start() {
|
||||
this.localStartVal = this.startVal;
|
||||
this.startTime = null;
|
||||
this.localDuration = this.duration;
|
||||
this.paused = false;
|
||||
this.rAF = this.requestAnimationFrame(this.count);
|
||||
},
|
||||
// 暂定状态,重新再开始滚动;或者滚动状态下,暂停
|
||||
reStart() {
|
||||
if (this.paused) {
|
||||
this.resume();
|
||||
this.paused = false;
|
||||
} else {
|
||||
this.stop();
|
||||
this.paused = true;
|
||||
}
|
||||
},
|
||||
// 暂停
|
||||
stop() {
|
||||
this.cancelAnimationFrame(this.rAF);
|
||||
},
|
||||
// 重新开始(暂停的情况下)
|
||||
resume() {
|
||||
this.startTime = null;
|
||||
this.localDuration = this.remaining;
|
||||
this.localStartVal = this.printVal;
|
||||
this.requestAnimationFrame(this.count);
|
||||
},
|
||||
// 重置
|
||||
reset() {
|
||||
this.startTime = null;
|
||||
this.cancelAnimationFrame(this.rAF);
|
||||
this.displayValue = this.formatNumber(this.startVal);
|
||||
},
|
||||
count(timestamp) {
|
||||
if (!this.startTime) this.startTime = timestamp;
|
||||
this.timestamp = timestamp;
|
||||
const progress = timestamp - this.startTime;
|
||||
this.remaining = this.localDuration - progress;
|
||||
if (this.useEasing) {
|
||||
if (this.countDown) {
|
||||
this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);
|
||||
} else {
|
||||
this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
|
||||
}
|
||||
} else {
|
||||
if (this.countDown) {
|
||||
this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
|
||||
} else {
|
||||
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
|
||||
}
|
||||
}
|
||||
if (this.countDown) {
|
||||
this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
|
||||
} else {
|
||||
this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
|
||||
}
|
||||
this.displayValue = this.formatNumber(this.printVal);
|
||||
if (progress < this.localDuration) {
|
||||
this.rAF = this.requestAnimationFrame(this.count);
|
||||
} else {
|
||||
this.$emit('end');
|
||||
}
|
||||
},
|
||||
// 判断是否数字
|
||||
isNumber(val) {
|
||||
return !isNaN(parseFloat(val));
|
||||
},
|
||||
formatNumber(num) {
|
||||
// 将num转为Number类型,因为其值可能为字符串数值,调用toFixed会报错
|
||||
num = Number(num);
|
||||
num = num.toFixed(Number(this.decimals));
|
||||
num += '';
|
||||
const x = num.split('.');
|
||||
let x1 = x[0];
|
||||
const x2 = x.length > 1 ? this.decimal + x[1] : '';
|
||||
const rgx = /(\d+)(\d{3})/;
|
||||
if (this.separator && !this.isNumber(this.separator)) {
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, '$1' + this.separator + '$2');
|
||||
}
|
||||
}
|
||||
return x1 + x2;
|
||||
},
|
||||
destroyed() {
|
||||
this.cancelAnimationFrame(this.rAF);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-count-num {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<view class="u-divider" :style="{
|
||||
height: height == 'auto' ? 'auto' : height + 'rpx',
|
||||
backgroundColor: bgColor,
|
||||
marginBottom: marginBottom + 'rpx',
|
||||
marginTop: marginTop + 'rpx'
|
||||
}" @tap="click">
|
||||
<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view>
|
||||
<view v-if="useSlot" class="u-divider-text" :style="{
|
||||
color: color,
|
||||
fontSize: fontSize + 'rpx'
|
||||
}"><slot /></view>
|
||||
<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* divider 分割线
|
||||
* @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。
|
||||
* @tutorial https://www.uviewui.com/components/divider.html
|
||||
* @property {String Number} half-width 文字左或右边线条宽度,数值或百分比,数值时单位为rpx
|
||||
* @property {String} border-color 线条颜色,优先级高于type(默认#dcdfe6)
|
||||
* @property {String} color 文字颜色(默认#909399)
|
||||
* @property {String Number} fontSize 字体大小,单位rpx(默认26)
|
||||
* @property {String} bg-color 整个divider的背景颜色(默认呢#ffffff)
|
||||
* @property {String Number} height 整个divider的高度,单位rpx(默认40)
|
||||
* @property {String} type 将线条设置主题色(默认primary)
|
||||
* @property {Boolean} useSlot 是否使用slot传入内容,如果不传入,中间不会有空隙(默认true)
|
||||
* @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0)
|
||||
* @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0)
|
||||
* @event {Function} click divider组件被点击时触发
|
||||
* @example <u-divider color="#fa3534">长河落日圆</u-divider>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-divider',
|
||||
props: {
|
||||
// 单一边divider横线的宽度(数值),单位rpx。或者百分比
|
||||
halfWidth: {
|
||||
type: [Number, String],
|
||||
default: 150
|
||||
},
|
||||
// divider横线的颜色,如设置,
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#dcdfe6'
|
||||
},
|
||||
// 主题色,可以是primary|info|success|warning|error之一值
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
// 文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#909399'
|
||||
},
|
||||
// 文字大小,单位rpx
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
},
|
||||
// 整个divider的背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// 整个divider的高度单位rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
// 上边距
|
||||
marginTop: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 下边距
|
||||
marginBottom: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否使用slot传入内容,如果不用slot传入内容,先的中间就不会有空隙
|
||||
useSlot: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lineStyle() {
|
||||
let style = {};
|
||||
if(String(this.halfWidth).indexOf('%') != -1) style.width = this.halfWidth;
|
||||
else style.width = this.halfWidth + 'rpx';
|
||||
// borderColor优先级高于type值
|
||||
if(this.borderColor) style.borderColor = this.borderColor;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
.u-divider {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.u-divider-line {
|
||||
border-bottom: 1px solid $u-border-color;
|
||||
transform: scale(1, 0.5);
|
||||
transform-origin: center;
|
||||
|
||||
&--bordercolor--primary {
|
||||
border-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--bordercolor--success {
|
||||
border-color: $u-type-success;
|
||||
}
|
||||
|
||||
&--bordercolor--error {
|
||||
border-color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--bordercolor--info {
|
||||
border-color: $u-type-info;
|
||||
}
|
||||
|
||||
&--bordercolor--warning {
|
||||
border-color: $u-type-warning;
|
||||
}
|
||||
}
|
||||
|
||||
.u-divider-text {
|
||||
white-space: nowrap;
|
||||
padding: 0 16rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<view class="u-dropdown-item" v-if="active" @touchmove.stop.prevent="() => {}" @tap.stop.prevent="() => {}">
|
||||
<block v-if="!$slots.default && !$slots.$default">
|
||||
<scroll-view scroll-y="true" :style="{
|
||||
height: $u.addUnit(height)
|
||||
}">
|
||||
<view class="u-dropdown-item__options">
|
||||
<u-cell-group>
|
||||
<u-cell-item @click="cellClick(item.value)" :arrow="false" :title="item.label" v-for="(item, index) in options"
|
||||
:key="index" :title-style="{
|
||||
color: value == item.value ? activeColor : inactiveColor
|
||||
}">
|
||||
<u-icon v-if="value == item.value" name="checkbox-mark" :color="activeColor" size="32"></u-icon>
|
||||
</u-cell-item>
|
||||
</u-cell-group>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</block>
|
||||
<slot v-else />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* dropdown-item 下拉菜单
|
||||
* @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景
|
||||
* @tutorial http://uviewui.com/components/dropdown.html
|
||||
* @property {String | Number} v-model 双向绑定选项卡选择值
|
||||
* @property {String} title 菜单项标题
|
||||
* @property {Array[Object]} options 选项数据,如果传入了默认slot,此参数无效
|
||||
* @property {Boolean} disabled 是否禁用此选项卡(默认false)
|
||||
* @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300)
|
||||
* @property {String | Number} height 弹窗下拉内容的高度(内容超出将会滚动)(默认auto)
|
||||
* @example <u-dropdown-item title="标题"></u-dropdown-item>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-dropdown-item',
|
||||
props: {
|
||||
// 当前选中项的value值
|
||||
value: {
|
||||
type: [Number, String, Array],
|
||||
default: ''
|
||||
},
|
||||
// 菜单项标题
|
||||
title: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 选项数据,如果传入了默认slot,此参数无效
|
||||
options: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否禁用此菜单项
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 下拉弹窗的高度
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false, // 当前项是否处于展开状态
|
||||
activeColor: '#2979ff', // 激活时左边文字和右边对勾图标的颜色
|
||||
inactiveColor: '#606266', // 未激活时左边文字和右边对勾图标的颜色
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 监听props是否发生了变化,有些值需要传递给父组件u-dropdown,无法双向绑定
|
||||
propsChange() {
|
||||
return `${this.title}-${this.disabled}`;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
propsChange(n) {
|
||||
// 当值变化时,通知父组件重新初始化,让父组件执行每个子组件的init()方法
|
||||
// 将所有子组件数据重新整理一遍
|
||||
if (this.parent) this.parent.init();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 父组件的实例
|
||||
this.parent = false;
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 获取父组件u-dropdown
|
||||
let parent = this.$u.$parent.call(this, 'u-dropdown');
|
||||
if (parent) {
|
||||
this.parent = parent;
|
||||
// 将子组件的激活颜色配置为父组件设置的激活和未激活时的颜色
|
||||
this.activeColor = parent.activeColor;
|
||||
this.inactiveColor = parent.inactiveColor;
|
||||
// 将本组件的this,放入到父组件的children数组中,让父组件可以操作本(子)组件的方法和属性
|
||||
// push进去前,显判断是否已经存在了本实例,因为在子组件内部数据变化时,会通过父组件重新初始化子组件
|
||||
let exist = parent.children.find(val => {
|
||||
return this === val;
|
||||
})
|
||||
if (!exist) parent.children.push(this);
|
||||
if (parent.children.length == 1) this.active = true;
|
||||
// 父组件无法监听children的变化,故将子组件的title,传入父组件的menuList数组中
|
||||
parent.menuList.push({
|
||||
title: this.title,
|
||||
disabled: this.disabled
|
||||
});
|
||||
}
|
||||
},
|
||||
// cell被点击
|
||||
cellClick(value) {
|
||||
// 修改通过v-model绑定的值
|
||||
this.$emit('input', value);
|
||||
// 通知父组件(u-dropdown)收起菜单
|
||||
this.parent.close();
|
||||
// 发出事件,抛出当前勾选项的value
|
||||
this.$emit('change', value);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
</style>
|
|
@ -0,0 +1,298 @@
|
|||
<template>
|
||||
<view class="u-dropdown">
|
||||
<view class="u-dropdown__menu" :style="{
|
||||
height: $u.addUnit(height)
|
||||
}" :class="{
|
||||
'u-border-bottom': borderBottom
|
||||
}">
|
||||
<view class="u-dropdown__menu__item" v-for="(item, index) in menuList" :key="index" @tap.stop="menuClick(index)">
|
||||
<view class="u-flex">
|
||||
<text class="u-dropdown__menu__item__text" :style="{
|
||||
color: item.disabled ? '#c0c4cc' : (index === current || highlightIndex == index) ? activeColor : inactiveColor,
|
||||
fontSize: $u.addUnit(titleSize)
|
||||
}">{{item.title}}</text>
|
||||
<view class="u-dropdown__menu__item__arrow" :class="{
|
||||
'u-dropdown__menu__item__arrow--rotate': index === current
|
||||
}">
|
||||
<u-icon :custom-style="{display: 'flex'}" :name="menuIcon" :size="$u.addUnit(menuIconSize)" :color="index === current || highlightIndex == index ? activeColor : '#c0c4cc'"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-dropdown__content" :style="[contentStyle, {
|
||||
transition: `opacity ${duration / 1000}s linear`,
|
||||
top: $u.addUnit(height),
|
||||
height: contentHeight + 'px'
|
||||
}]"
|
||||
@tap="maskClick" @touchmove.stop.prevent>
|
||||
<view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="u-dropdown__content__mask"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* dropdown 下拉菜单
|
||||
* @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景
|
||||
* @tutorial http://uviewui.com/components/dropdown.html
|
||||
* @property {String} active-color 标题和选项卡选中的颜色(默认#2979ff)
|
||||
* @property {String} inactive-color 标题和选项卡未选中的颜色(默认#606266)
|
||||
* @property {Boolean} close-on-click-mask 点击遮罩是否关闭菜单(默认true)
|
||||
* @property {Boolean} close-on-click-self 点击当前激活项标题是否关闭菜单(默认true)
|
||||
* @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300)
|
||||
* @property {String | Number} height 标题菜单的高度,单位任意(默认80)
|
||||
* @property {String | Number} border-radius 菜单展开内容下方的圆角值,单位任意(默认0)
|
||||
* @property {Boolean} border-bottom 标题菜单是否显示下边框(默认false)
|
||||
* @property {String | Number} title-size 标题的字体大小,单位任意,数值默认为rpx单位(默认28)
|
||||
* @event {Function} open 下拉菜单被打开时触发
|
||||
* @event {Function} close 下拉菜单被关闭时触发
|
||||
* @example <u-dropdown></u-dropdown>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-dropdown',
|
||||
props: {
|
||||
// 菜单标题和选项的激活态颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 菜单标题和选项的未激活态颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 点击遮罩是否关闭菜单
|
||||
closeOnClickMask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 点击当前激活项标题是否关闭菜单
|
||||
closeOnClickSelf: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 过渡时间
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 300
|
||||
},
|
||||
// 标题菜单的高度,单位任意,数值默认为rpx单位
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 80
|
||||
},
|
||||
// 是否显示下边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题的字体大小
|
||||
titleSize: {
|
||||
type: [Number, String],
|
||||
default: 28
|
||||
},
|
||||
// 下拉出来的内容部分的圆角值
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 菜单右侧的icon图标
|
||||
menuIcon: {
|
||||
type: String,
|
||||
default: 'arrow-down'
|
||||
},
|
||||
// 菜单右侧图标的大小
|
||||
menuIconSize: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDropdown: true, // 是否打开下来菜单,
|
||||
menuList: [], // 显示的菜单
|
||||
active: false, // 下拉菜单的状态
|
||||
// 当前是第几个菜单处于激活状态,小程序中此处不能写成false或者"",否则后续将current赋值为0,
|
||||
// 无能的TX没有使用===而是使用==判断,导致程序认为前后二者没有变化,从而不会触发视图更新
|
||||
current: 99999,
|
||||
// 外层内容的样式,初始时处于底层,且透明
|
||||
contentStyle: {
|
||||
zIndex: -1,
|
||||
opacity: 0
|
||||
},
|
||||
// 让某个菜单保持高亮的状态
|
||||
highlightIndex: 99999,
|
||||
contentHeight: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 下拉出来部分的样式
|
||||
popupStyle() {
|
||||
let style = {};
|
||||
// 进行Y轴位移,展开状态时,恢复原位。收齐状态时,往上位移100%,进行隐藏
|
||||
style.transform = `translateY(${this.active ? 0 : '-100%'})`
|
||||
style['transition-duration'] = this.duration / 1000 + 's';
|
||||
style.borderRadius = `0 0 ${this.$u.addUnit(this.borderRadius)} ${this.$u.addUnit(this.borderRadius)}`;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 引用所有子组件(u-dropdown-item)的this,不能在data中声明变量,否则在微信小程序会造成循环引用而报错
|
||||
this.children = [];
|
||||
},
|
||||
mounted() {
|
||||
this.getContentHeight();
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 当某个子组件内容变化时,触发父组件的init,父组件再让每一个子组件重新初始化一遍
|
||||
// 以保证数据的正确性
|
||||
this.menuList = [];
|
||||
this.children.map(child => {
|
||||
child.init();
|
||||
})
|
||||
},
|
||||
// 点击菜单
|
||||
menuClick(index) {
|
||||
// 判断是否被禁用
|
||||
if (this.menuList[index].disabled) return;
|
||||
// 如果点击时的索引和当前激活项索引相同,意味着点击了激活项,需要收起下拉菜单
|
||||
if (index === this.current && this.closeOnClickSelf) {
|
||||
this.close();
|
||||
// 等动画结束后,再移除下拉菜单中的内容,否则直接移除,也就没有下拉菜单收起的效果了
|
||||
setTimeout(() => {
|
||||
this.children[index].active = false;
|
||||
}, this.duration)
|
||||
return;
|
||||
}
|
||||
this.open(index);
|
||||
},
|
||||
// 打开下拉菜单
|
||||
open(index) {
|
||||
// 重置高亮索引,否则会造成多个菜单同时高亮
|
||||
// this.highlightIndex = 9999;
|
||||
// 展开时,设置下拉内容的样式
|
||||
this.contentStyle = {
|
||||
zIndex: 11,
|
||||
}
|
||||
// 标记展开状态以及当前展开项的索引
|
||||
this.active = true;
|
||||
this.current = index;
|
||||
// 历遍所有的子元素,将索引匹配的项标记为激活状态,因为子元素是通过v-if控制切换的
|
||||
// 之所以不是因display: none,是因为nvue没有display这个属性
|
||||
this.children.map((val, idx) => {
|
||||
val.active = index == idx ? true : false;
|
||||
})
|
||||
this.$emit('open', this.current);
|
||||
},
|
||||
// 设置下拉菜单处于收起状态
|
||||
close() {
|
||||
this.$emit('close', this.current);
|
||||
// 设置为收起状态,同时current归位,设置为空字符串
|
||||
this.active = false;
|
||||
this.current = 99999;
|
||||
// 下拉内容的样式进行调整,不透明度设置为0
|
||||
this.contentStyle = {
|
||||
zIndex: -1,
|
||||
opacity: 0
|
||||
}
|
||||
},
|
||||
// 点击遮罩
|
||||
maskClick() {
|
||||
// 如果不允许点击遮罩,直接返回
|
||||
if (!this.closeOnClickMask) return;
|
||||
this.close();
|
||||
},
|
||||
// 外部手动设置某个菜单高亮
|
||||
highlight(index = undefined) {
|
||||
this.highlightIndex = index !== undefined ? index : 99999;
|
||||
},
|
||||
// 获取下拉菜单内容的高度
|
||||
getContentHeight() {
|
||||
// 这里的原理为,因为dropdown组件是相对定位的,它的下拉出来的内容,必须给定一个高度
|
||||
// 才能让遮罩占满菜单一下,直到屏幕底部的高度
|
||||
// this.$u.sys()为uView封装的获取设备信息的方法
|
||||
let windowHeight = this.$u.sys().windowHeight;
|
||||
this.$uGetRect('.u-dropdown__menu').then(res => {
|
||||
// 这里获取的是dropdown的尺寸,在H5上,uniapp获取尺寸是有bug的(以前提出修复过,后来又出现了此bug,目前hx2.8.11版本)
|
||||
// H5端bug表现为元素尺寸的top值为导航栏底部到到元素的上边沿的距离,但是元素的bottom值确是导航栏顶部到元素底部的距离
|
||||
// 二者是互相矛盾的,本质原因是H5端导航栏非原生,uni的开发者大意造成
|
||||
// 这里取菜单栏的botton值合理的,不能用res.top,否则页面会造成滚动
|
||||
this.contentHeight = windowHeight - res.bottom;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-dropdown {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
&__menu {
|
||||
@include vue-flex;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
height: 80rpx;
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&__text {
|
||||
font-size: 28rpx;
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
margin-left: 6rpx;
|
||||
transition: transform .3s;
|
||||
align-items: center;
|
||||
@include vue-flex;
|
||||
|
||||
&--rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: absolute;
|
||||
z-index: 8;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
&__mask {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
background: rgba(0, 0, 0, .3);
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&__popup {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
transition: all 0.3s;
|
||||
transform: translate3D(0, -100%, 0);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,193 @@
|
|||
<template>
|
||||
<view class="u-empty" v-if="show" :style="{
|
||||
marginTop: marginTop + 'rpx'
|
||||
}">
|
||||
<u-icon
|
||||
:name="src ? src : 'empty-' + mode"
|
||||
:custom-style="iconStyle"
|
||||
:label="text ? text : icons[mode]"
|
||||
label-pos="bottom"
|
||||
:label-color="color"
|
||||
:label-size="fontSize"
|
||||
:size="iconSize"
|
||||
:color="iconColor"
|
||||
margin-top="14"
|
||||
></u-icon>
|
||||
<view class="u-slot-wrap">
|
||||
<slot name="bottom"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* empty 内容为空
|
||||
* @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。
|
||||
* @tutorial https://www.uviewui.com/components/empty.html
|
||||
* @property {String} color 文字颜色(默认#c0c4cc)
|
||||
* @property {String} text 文字提示(默认“无内容”)
|
||||
* @property {String} src 自定义图标路径,如定义,mode参数会失效
|
||||
* @property {String Number} font-size 提示文字的大小,单位rpx(默认28)
|
||||
* @property {String} mode 内置的图标,见官网说明(默认data)
|
||||
* @property {String Number} img-width 图标的宽度,单位rpx(默认240)
|
||||
* @property {String} img-height 图标的高度,单位rpx(默认auto)
|
||||
* @property {String Number} margin-top 组件距离上一个元素之间的距离(默认0)
|
||||
* @property {Boolean} show 是否显示组件(默认true)
|
||||
* @event {Function} click 点击组件时触发
|
||||
* @event {Function} close 点击关闭按钮时触发
|
||||
* @example <u-empty text="所谓伊人,在水一方" mode="list"></u-empty>
|
||||
*/
|
||||
export default {
|
||||
name: "u-empty",
|
||||
props: {
|
||||
// 图标路径
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 提示文字
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#c0c4cc'
|
||||
},
|
||||
// 图标的颜色
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#c0c4cc'
|
||||
},
|
||||
// 图标的大小
|
||||
iconSize: {
|
||||
type: [String, Number],
|
||||
default: 120
|
||||
},
|
||||
// 文字大小,单位rpx
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: 26
|
||||
},
|
||||
// 选择预置的图标类型
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'data'
|
||||
},
|
||||
// 图标宽度,单位rpx
|
||||
imgWidth: {
|
||||
type: [String, Number],
|
||||
default: 120
|
||||
},
|
||||
// 图标高度,单位rpx
|
||||
imgHeight: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否显示组件
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 组件距离上一个元素之间的距离
|
||||
marginTop: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
icons: {
|
||||
car: '购物车为空',
|
||||
page: '页面不存在',
|
||||
search: '没有搜索结果',
|
||||
address: '没有收货地址',
|
||||
wifi: '没有WiFi',
|
||||
order: '订单为空',
|
||||
coupon: '没有优惠券',
|
||||
favor: '暂无收藏',
|
||||
permission: '无权限',
|
||||
history: '无历史记录',
|
||||
news: '无新闻列表',
|
||||
message: '消息列表为空',
|
||||
list: '列表为空',
|
||||
data: '数据为空'
|
||||
},
|
||||
// icons: [{
|
||||
// icon: 'car',
|
||||
// text: '购物车为空'
|
||||
// },{
|
||||
// icon: 'page',
|
||||
// text: '页面不存在'
|
||||
// },{
|
||||
// icon: 'search',
|
||||
// text: '没有搜索结果'
|
||||
// },{
|
||||
// icon: 'address',
|
||||
// text: '没有收货地址'
|
||||
// },{
|
||||
// icon: 'wifi',
|
||||
// text: '没有WiFi'
|
||||
// },{
|
||||
// icon: 'order',
|
||||
// text: '订单为空'
|
||||
// },{
|
||||
// icon: 'coupon',
|
||||
// text: '没有优惠券'
|
||||
// },{
|
||||
// icon: 'favor',
|
||||
// text: '暂无收藏'
|
||||
// },{
|
||||
// icon: 'permission',
|
||||
// text: '无权限'
|
||||
// },{
|
||||
// icon: 'history',
|
||||
// text: '无历史记录'
|
||||
// },{
|
||||
// icon: 'news',
|
||||
// text: '无新闻列表'
|
||||
// },{
|
||||
// icon: 'message',
|
||||
// text: '消息列表为空'
|
||||
// },{
|
||||
// icon: 'list',
|
||||
// text: '列表为空'
|
||||
// },{
|
||||
// icon: 'data',
|
||||
// text: '数据为空'
|
||||
// }],
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-empty {
|
||||
@include vue-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.u-image {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.u-slot-wrap {
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,384 @@
|
|||
<template>
|
||||
<view class="u-field" :class="{'u-border-top': borderTop, 'u-border-bottom': borderBottom }">
|
||||
<view class="u-field-inner" :class="[type == 'textarea' ? 'u-textarea-inner' : '', 'u-label-postion-' + labelPosition]">
|
||||
<view class="u-label" :class="[required ? 'u-required' : '']" :style="{
|
||||
justifyContent: justifyContent,
|
||||
flex: labelPosition == 'left' ? `0 0 ${labelWidth}rpx` : '1'
|
||||
}">
|
||||
<view class="u-icon-wrap" v-if="icon">
|
||||
<u-icon size="32" :custom-style="iconStyle" :name="icon" :color="iconColor" class="u-icon"></u-icon>
|
||||
</view>
|
||||
<slot name="icon"></slot>
|
||||
<text class="u-label-text" :class="[this.$slots.icon || icon ? 'u-label-left-gap' : '']">{{ label }}</text>
|
||||
</view>
|
||||
<view class="fild-body">
|
||||
<view class="u-flex-1 u-flex" :style="[inputWrapStyle]">
|
||||
<textarea v-if="type == 'textarea'" class="u-flex-1 u-textarea-class" :style="[fieldStyle]" :value="value"
|
||||
:placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength"
|
||||
:focus="focus" :autoHeight="autoHeight" :fixed="fixed" @input="onInput" @blur="onBlur" @focus="onFocus" @confirm="onConfirm"
|
||||
@tap="fieldClick" />
|
||||
<input
|
||||
v-else
|
||||
:style="[fieldStyle]"
|
||||
:type="type"
|
||||
class="u-flex-1 u-field__input-wrap"
|
||||
:value="value"
|
||||
:password="password || this.type === 'password'"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled"
|
||||
:maxlength="inputMaxlength"
|
||||
:focus="focus"
|
||||
:confirmType="confirmType"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@input="onInput"
|
||||
@confirm="onConfirm"
|
||||
@tap="fieldClick"
|
||||
/>
|
||||
</view>
|
||||
<u-icon :size="clearSize" v-if="clearable && value != '' && focused" name="close-circle-fill" color="#c0c4cc" class="u-clear-icon" @click="onClear"/>
|
||||
<view class="u-button-wrap"><slot name="right" /></view>
|
||||
<u-icon v-if="rightIcon" @click="rightIconClick" :name="rightIcon" color="#c0c4cc" :style="[rightIconStyle]" size="26" class="u-arror-right" />
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="errorMessage !== false && errorMessage != ''" class="u-error-message" :style="{
|
||||
paddingLeft: labelWidth + 'rpx'
|
||||
}">{{ errorMessage }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* field 输入框
|
||||
* @description 借助此组件,可以实现表单的输入, 有"text"和"textarea"类型的,此外,借助uView的picker和actionSheet组件可以快速实现上拉菜单,时间,地区选择等, 为表单解决方案的利器。
|
||||
* @tutorial https://www.uviewui.com/components/field.html
|
||||
* @property {String} type 输入框的类型(默认text)
|
||||
* @property {String} icon label左边的图标,限uView的图标名称
|
||||
* @property {Object} icon-style 左边图标的样式,对象形式
|
||||
* @property {Boolean} right-icon 输入框右边的图标名称,限uView的图标名称(默认false)
|
||||
* @property {Boolean} required 是否必填,左边您显示红色"*"号(默认false)
|
||||
* @property {String} label 输入框左边的文字提示
|
||||
* @property {Boolean} password 是否密码输入方式(用点替换文字),type为text时有效(默认false)
|
||||
* @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容,且获得焦点时才显示),点击可清空输入框内容(默认true)
|
||||
* @property {Number String} label-width label的宽度,单位rpx(默认130)
|
||||
* @property {String} label-align label的文字对齐方式(默认left)
|
||||
* @property {Object} field-style 自定义输入框的样式,对象形式
|
||||
* @property {Number | String} clear-size 清除图标的大小,单位rpx(默认30)
|
||||
* @property {String} input-align 输入框内容对齐方式(默认left)
|
||||
* @property {Boolean} border-bottom 是否显示field的下边框(默认true)
|
||||
* @property {Boolean} border-top 是否显示field的上边框(默认false)
|
||||
* @property {String} icon-color 左边通过icon配置的图标的颜色(默认#606266)
|
||||
* @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true)
|
||||
* @property {String Boolean} error-message 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
|
||||
* @property {String} placeholder 输入框的提示文字
|
||||
* @property {String} placeholder-style placeholder的样式(内联样式,字符串),如"color: #ddd"
|
||||
* @property {Boolean} focus 是否自动获得焦点(默认false)
|
||||
* @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false)
|
||||
* @property {Boolean} disabled 是否不可输入(默认false)
|
||||
* @property {Number String} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140)
|
||||
* @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done)
|
||||
* @event {Function} input 输入框内容发生变化时触发
|
||||
* @event {Function} focus 输入框获得焦点时触发
|
||||
* @event {Function} blur 输入框失去焦点时触发
|
||||
* @event {Function} confirm 点击完成按钮时触发
|
||||
* @event {Function} right-icon-click 通过right-icon生成的图标被点击时触发
|
||||
* @event {Function} click 输入框被点击或者通过right-icon生成的图标被点击时触发,这样设计是考虑到传递右边的图标,一般都为需要弹出"picker"等操作时的场景,点击倒三角图标,理应发出此事件,见上方说明
|
||||
* @example <u-field v-model="mobile" label="手机号" required :error-message="errorMessage"></u-field>
|
||||
*/
|
||||
export default {
|
||||
name:"u-field",
|
||||
props: {
|
||||
icon: String,
|
||||
rightIcon: String,
|
||||
// arrowDirection: {
|
||||
// type: String,
|
||||
// default: 'right'
|
||||
// },
|
||||
required: Boolean,
|
||||
label: String,
|
||||
password: Boolean,
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边标题的宽度单位rpx
|
||||
labelWidth: {
|
||||
type: [Number, String],
|
||||
default: 130
|
||||
},
|
||||
// 对齐方式,left|center|right
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
inputAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
errorMessage: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
placeholder: String,
|
||||
placeholderStyle: String,
|
||||
focus: Boolean,
|
||||
fixed: Boolean,
|
||||
value: [Number, String],
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxlength: {
|
||||
type: [Number, String],
|
||||
default: 140
|
||||
},
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'done'
|
||||
},
|
||||
// lable的位置,可选为 left-左边,top-上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 输入框的自定义样式
|
||||
fieldStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 清除按钮的大小
|
||||
clearSize: {
|
||||
type: [Number, String],
|
||||
default: 30
|
||||
},
|
||||
// lable左边的图标样式,对象形式
|
||||
iconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示上边框
|
||||
borderTop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示下边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否自动去除两端的空格
|
||||
trim: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
focused: false,
|
||||
itemIndex: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
inputWrapStyle() {
|
||||
let style = {};
|
||||
style.textAlign = this.inputAlign;
|
||||
// 判断lable的位置,如果是left的话,让input左边两边有间隙
|
||||
if(this.labelPosition == 'left') {
|
||||
style.margin = `0 8rpx`;
|
||||
} else {
|
||||
// 如果lable是top的,input的左边就没必要有间隙了
|
||||
style.marginRight = `8rpx`;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
rightIconStyle() {
|
||||
let style = {};
|
||||
if (this.arrowDirection == 'top') style.transform = 'roate(-90deg)';
|
||||
if (this.arrowDirection == 'bottom') style.transform = 'roate(90deg)';
|
||||
else style.transform = 'roate(0deg)';
|
||||
return style;
|
||||
},
|
||||
labelStyle() {
|
||||
let style = {};
|
||||
if(this.labelAlign == 'left') style.justifyContent = 'flext-start';
|
||||
if(this.labelAlign == 'center') style.justifyContent = 'center';
|
||||
if(this.labelAlign == 'right') style.justifyContent = 'flext-end';
|
||||
return style;
|
||||
},
|
||||
// uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法
|
||||
justifyContent() {
|
||||
if(this.labelAlign == 'left') return 'flex-start';
|
||||
if(this.labelAlign == 'center') return 'center';
|
||||
if(this.labelAlign == 'right') return 'flex-end';
|
||||
},
|
||||
// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
|
||||
inputMaxlength() {
|
||||
return Number(this.maxlength)
|
||||
},
|
||||
// label的位置
|
||||
fieldInnerStyle() {
|
||||
let style = {};
|
||||
if(this.labelPosition == 'left') {
|
||||
style.flexDirection = 'row';
|
||||
} else {
|
||||
style.flexDirection = 'column';
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onInput(event) {
|
||||
let value = event.detail.value;
|
||||
// 判断是否去除空格
|
||||
if(this.trim) value = this.$u.trim(value);
|
||||
this.$emit('input', value);
|
||||
},
|
||||
onFocus(event) {
|
||||
this.focused = true;
|
||||
this.$emit('focus', event);
|
||||
},
|
||||
onBlur(event) {
|
||||
// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
|
||||
// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
|
||||
setTimeout(() => {
|
||||
this.focused = false;
|
||||
}, 100)
|
||||
this.$emit('blur', event);
|
||||
},
|
||||
onConfirm(e) {
|
||||
this.$emit('confirm', e.detail.value);
|
||||
},
|
||||
onClear(event) {
|
||||
this.$emit('input', '');
|
||||
},
|
||||
rightIconClick() {
|
||||
this.$emit('right-icon-click');
|
||||
this.$emit('click');
|
||||
},
|
||||
fieldClick() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-field {
|
||||
font-size: 28rpx;
|
||||
padding: 20rpx 28rpx;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
.u-field-inner {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-textarea-inner {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.u-textarea-class {
|
||||
min-height: 96rpx;
|
||||
width: auto;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.fild-body {
|
||||
@include vue-flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-arror-right {
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.u-label-text {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.u-label-left-gap {
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.u-label-postion-top {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.u-label {
|
||||
width: 130rpx;
|
||||
flex: 1 1 130rpx;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-required::before {
|
||||
content: '*';
|
||||
position: absolute;
|
||||
left: -16rpx;
|
||||
font-size: 14px;
|
||||
color: $u-type-error;
|
||||
height: 9px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.u-field__input-wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
height: 48rpx;
|
||||
flex: 1;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.u-clear-icon {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-error-message {
|
||||
color: $u-type-error;
|
||||
font-size: 26rpx;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.placeholder-style {
|
||||
color: rgb(150, 151, 153);
|
||||
}
|
||||
|
||||
.u-input-class {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.u-button-wrap {
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,431 @@
|
|||
<template>
|
||||
<view class="u-form-item" :class="{'u-border-bottom': elBorderBottom, 'u-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom')}">
|
||||
<view class="u-form-item__body" :style="{
|
||||
flexDirection: elLabelPosition == 'left' ? 'row' : 'column'
|
||||
}">
|
||||
<!-- 微信小程序中,将一个参数设置空字符串,结果会变成字符串"true" -->
|
||||
<view class="u-form-item--left" :style="{
|
||||
width: uLabelWidth,
|
||||
flex: `0 0 ${uLabelWidth}`,
|
||||
marginBottom: elLabelPosition == 'left' ? 0 : '10rpx',
|
||||
}">
|
||||
<!-- 为了块对齐 -->
|
||||
<view class="u-form-item--left__content" v-if="required || leftIcon || label">
|
||||
<!-- nvue不支持伪元素before -->
|
||||
<text v-if="required" class="u-form-item--left__content--required">*</text>
|
||||
<view class="u-form-item--left__content__icon" v-if="leftIcon">
|
||||
<u-icon :name="leftIcon" :custom-style="leftIconStyle"></u-icon>
|
||||
</view>
|
||||
<view class="u-form-item--left__content__label" :style="[elLabelStyle, {
|
||||
'justify-content': elLabelAlign == 'left' ? 'flex-start' : elLabelAlign == 'center' ? 'center' : 'flex-end'
|
||||
}]">
|
||||
{{label}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-form-item--right u-flex">
|
||||
<view class="u-form-item--right__content">
|
||||
<view class="u-form-item--right__content__slot ">
|
||||
<slot />
|
||||
</view>
|
||||
<view class="u-form-item--right__content__icon u-flex" v-if="$slots.right || rightIcon">
|
||||
<u-icon :custom-style="rightIconStyle" v-if="rightIcon" :name="rightIcon"></u-icon>
|
||||
<slot name="right" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-form-item__message" v-if="validateState === 'error' && showError('message')" :style="{
|
||||
paddingLeft: elLabelPosition == 'left' ? $u.addUnit(elLabelWidth) : '0',
|
||||
}">{{validateMessage}}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/util/emitter.js';
|
||||
import schema from '../../libs/util/async-validator';
|
||||
// 去除警告信息
|
||||
schema.warning = function() {};
|
||||
|
||||
/**
|
||||
* form-item 表单item
|
||||
* @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
|
||||
* @tutorial http://uviewui.com/components/form.html
|
||||
* @property {String} label 左侧提示文字
|
||||
* @property {Object} prop 表单域model对象的属性名,在使用 validate、resetFields 方法的情况下,该属性是必填的
|
||||
* @property {Boolean} border-bottom 是否显示表单域的下划线边框
|
||||
* @property {String} label-position 表单域提示文字的位置,left-左侧,top-上方
|
||||
* @property {String Number} label-width 提示文字的宽度,单位rpx(默认90)
|
||||
* @property {Object} label-style lable的样式,对象形式
|
||||
* @property {String} label-align lable的对齐方式
|
||||
* @property {String} right-icon 右侧自定义字体图标(限uView内置图标)或图片地址
|
||||
* @property {String} left-icon 左侧自定义字体图标(限uView内置图标)或图片地址
|
||||
* @property {Object} left-icon-style 左侧图标的样式,对象形式
|
||||
* @property {Object} right-icon-style 右侧图标的样式,对象形式
|
||||
* @property {Boolean} required 是否显示左边的"*"号,这里仅起展示作用,如需校验必填,请通过rules配置必填规则(默认false)
|
||||
* @example <u-form-item label="姓名"><u-input v-model="form.name" /></u-form-item>
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'u-form-item',
|
||||
mixins: [Emitter],
|
||||
inject: {
|
||||
uForm: {
|
||||
default () {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
// input的label提示语
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 绑定的值
|
||||
prop: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示表单域的下划线边框
|
||||
borderBottom: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
// label的位置,left-左边,top-上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// label的宽度,单位rpx
|
||||
labelWidth: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// lable的样式,对象形式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// lable字体的对齐方式
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 右侧图标
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 左侧图标
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 左侧图标的样式
|
||||
leftIconStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 左侧图标的样式
|
||||
rightIconStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
initialValue: '', // 存储的默认值
|
||||
// isRequired: false, // 是否必填,由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成
|
||||
validateState: '', // 是否校验成功
|
||||
validateMessage: '', // 校验失败的提示语
|
||||
// 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色,
|
||||
errorType: ['message'],
|
||||
fieldValue: '', // 获取当前子组件input的输入的值
|
||||
// 父组件的参数,在computed计算中,无法得知this.parent发生变化,故将父组件的参数值,放到data中
|
||||
parentData: {
|
||||
borderBottom: true,
|
||||
labelWidth: 90,
|
||||
labelPosition: 'left',
|
||||
labelStyle: {},
|
||||
labelAlign: 'left',
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
validateState(val) {
|
||||
this.broadcastInputError();
|
||||
},
|
||||
// 监听u-form组件的errorType的变化
|
||||
"uForm.errorType"(val) {
|
||||
this.errorType = val;
|
||||
this.broadcastInputError();
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 计算后的label宽度,由于需要多个判断,故放到computed中
|
||||
uLabelWidth() {
|
||||
// 如果用户设置label为空字符串(微信小程序空字符串最终会变成字符串的'true'),意味着要将label的位置宽度设置为auto
|
||||
return this.elLabelPosition == 'left' ? (this.label === 'true' || this.label === '' ? 'auto' : this.$u.addUnit(this
|
||||
.elLabelWidth)) : '100%';
|
||||
},
|
||||
showError() {
|
||||
return type => {
|
||||
// 如果errorType数组中含有none,或者toast提示类型
|
||||
if (this.errorType.indexOf('none') >= 0) return false;
|
||||
else if (this.errorType.indexOf(type) >= 0) return true;
|
||||
else return false;
|
||||
}
|
||||
},
|
||||
// label的宽度
|
||||
elLabelWidth() {
|
||||
// label默认宽度为90,优先使用本组件的值,如果没有(如果设置为0,也算是配置了值,依然起效),则用u-form的值
|
||||
return (this.labelWidth != 0 || this.labelWidth != '') ? this.labelWidth : (this.parentData.labelWidth ? this.parentData
|
||||
.labelWidth :
|
||||
90);
|
||||
},
|
||||
// label的样式
|
||||
elLabelStyle() {
|
||||
return Object.keys(this.labelStyle).length ? this.labelStyle : (this.parentData.labelStyle ? this.parentData.labelStyle :
|
||||
{});
|
||||
},
|
||||
// label的位置,左侧或者上方
|
||||
elLabelPosition() {
|
||||
return this.labelPosition ? this.labelPosition : (this.parentData.labelPosition ? this.parentData.labelPosition :
|
||||
'left');
|
||||
},
|
||||
// label的对齐方式
|
||||
elLabelAlign() {
|
||||
return this.labelAlign ? this.labelAlign : (this.parentData.labelAlign ? this.parentData.labelAlign : 'left');
|
||||
},
|
||||
// label的下划线
|
||||
elBorderBottom() {
|
||||
// 子组件的borderBottom默认为空字符串,如果不等于空字符串,意味着子组件设置了值,优先使用子组件的值
|
||||
return this.borderBottom !== '' ? this.borderBottom : this.parentData.borderBottom ? this.parentData.borderBottom :
|
||||
true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
broadcastInputError() {
|
||||
// 子组件发出事件,第三个参数为true或者false,true代表有错误
|
||||
this.broadcast('u-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border'));
|
||||
},
|
||||
// 判断是否需要required校验
|
||||
setRules() {
|
||||
let that = this;
|
||||
// 由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成
|
||||
// 从父组件u-form拿到当前u-form-item需要验证 的规则
|
||||
// let rules = this.getRules();
|
||||
// if (rules.length) {
|
||||
// this.isRequired = rules.some(rule => {
|
||||
// // 如果有必填项,就返回,没有的话,就是undefined
|
||||
// return rule.required;
|
||||
// });
|
||||
// }
|
||||
|
||||
// blur事件
|
||||
this.$on('on-form-blur', that.onFieldBlur);
|
||||
// change事件
|
||||
this.$on('on-form-change', that.onFieldChange);
|
||||
},
|
||||
|
||||
// 从u-form的rules属性中,取出当前u-form-item的校验规则
|
||||
getRules() {
|
||||
// 父组件的所有规则
|
||||
let rules = this.parent.rules;
|
||||
rules = rules ? rules[this.prop] : [];
|
||||
// 保证返回的是一个数组形式
|
||||
return [].concat(rules || []);
|
||||
},
|
||||
|
||||
// blur事件时进行表单校验
|
||||
onFieldBlur() {
|
||||
this.validation('blur');
|
||||
},
|
||||
|
||||
// change事件进行表单校验
|
||||
onFieldChange() {
|
||||
this.validation('change');
|
||||
},
|
||||
|
||||
// 过滤出符合要求的rule规则
|
||||
getFilteredRule(triggerType = '') {
|
||||
let rules = this.getRules();
|
||||
// 整体验证表单时,triggerType为空字符串,此时返回所有规则进行验证
|
||||
if (!triggerType) return rules;
|
||||
// 历遍判断规则是否有对应的事件,比如blur,change触发等的事件
|
||||
// 使用indexOf判断,是因为某些时候设置的验证规则的trigger属性可能为多个,比如['blur','change']
|
||||
// 某些场景可能的判断规则,可能不存在trigger属性,故先判断是否存在此属性
|
||||
return rules.filter(res => res.trigger && res.trigger.indexOf(triggerType) !== -1);
|
||||
},
|
||||
|
||||
// 校验数据
|
||||
validation(trigger, callback = () => {}) {
|
||||
// 检验之间,先获取需要校验的值
|
||||
this.fieldValue = this.parent.model[this.prop];
|
||||
// blur和change是否有当前方式的校验规则
|
||||
let rules = this.getFilteredRule(trigger);
|
||||
// 判断是否有验证规则,如果没有规则,也调用回调方法,否则父组件u-form会因为
|
||||
// 对count变量的统计错误而无法进入上一层的回调
|
||||
if (!rules || rules.length === 0) {
|
||||
return callback('');
|
||||
}
|
||||
// 设置当前的装填,标识为校验中
|
||||
this.validateState = 'validating';
|
||||
// 调用async-validator的方法
|
||||
let validator = new schema({
|
||||
[this.prop]: rules
|
||||
});
|
||||
validator.validate({
|
||||
[this.prop]: this.fieldValue
|
||||
}, {
|
||||
firstFields: true
|
||||
}, (errors, fields) => {
|
||||
// 记录状态和报错信息
|
||||
this.validateState = !errors ? 'success' : 'error';
|
||||
this.validateMessage = errors ? errors[0].message : '';
|
||||
// 调用回调方法
|
||||
callback(this.validateMessage);
|
||||
});
|
||||
},
|
||||
|
||||
// 清空当前的u-form-item
|
||||
resetField() {
|
||||
this.parent.model[this.prop] = this.initialValue;
|
||||
// 设置为`success`状态,只是为了清空错误标记
|
||||
this.validateState = 'success';
|
||||
}
|
||||
},
|
||||
|
||||
// 组件创建完成时,将当前实例保存到u-form中
|
||||
mounted() {
|
||||
// 支付宝、头条小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
|
||||
this.parent = this.$u.$parent.call(this, 'u-form');
|
||||
if (this.parent) {
|
||||
// 历遍parentData中的属性,将parent中的同名属性赋值给parentData
|
||||
Object.keys(this.parentData).map(key => {
|
||||
this.parentData[key] = this.parent[key];
|
||||
});
|
||||
// 如果没有传入prop,或者uForm为空(如果u-form-input单独使用,就不会有uForm注入),就不进行校验
|
||||
if (this.prop) {
|
||||
// 将本实例添加到父组件中
|
||||
this.parent.fields.push(this);
|
||||
this.errorType = this.parent.errorType;
|
||||
// 设置初始值
|
||||
this.initialValue = this.fieldValue;
|
||||
// 添加表单校验,这里必须要写在$nextTick中,因为u-form的rules是通过ref手动传入的
|
||||
// 不在$nextTick中的话,可能会造成执行此处代码时,父组件还没通过ref把规则给u-form,导致规则为空
|
||||
this.$nextTick(() => {
|
||||
this.setRules();
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 组件销毁前,将实例从u-form的缓存中移除
|
||||
beforeDestroy() {
|
||||
// 如果当前没有prop的话表示当前不要进行删除(因为没有注入)
|
||||
if (this.parent && this.prop) {
|
||||
this.parent.fields.map((item, index) => {
|
||||
if (item === this) this.parent.fields.splice(index, 1);
|
||||
})
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-form-item {
|
||||
@include vue-flex;
|
||||
// align-items: flex-start;
|
||||
padding: 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: $u-main-color;
|
||||
box-sizing: border-box;
|
||||
line-height: $u-form-item-height;
|
||||
flex-direction: column;
|
||||
|
||||
&__border-bottom--error:after {
|
||||
border-color: $u-type-error;
|
||||
}
|
||||
|
||||
&__body {
|
||||
@include vue-flex;
|
||||
}
|
||||
|
||||
&--left {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
padding-right: 10rpx;
|
||||
flex: 1;
|
||||
|
||||
&__icon {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
&--required {
|
||||
position: absolute;
|
||||
left: -16rpx;
|
||||
vertical-align: middle;
|
||||
color: $u-type-error;
|
||||
padding-top: 6rpx;
|
||||
}
|
||||
|
||||
&__label {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--right {
|
||||
flex: 1;
|
||||
|
||||
&__content {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
&__slot {
|
||||
flex: 1;
|
||||
/* #ifndef MP */
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-left: 10rpx;
|
||||
color: $u-light-color;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__message {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: $u-type-error;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<view class="u-form"><slot /></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* form 表单
|
||||
* @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
|
||||
* @tutorial http://uviewui.com/components/form.html
|
||||
* @property {Object} model 表单数据对象
|
||||
* @property {Boolean} border-bottom 是否显示表单域的下划线边框
|
||||
* @property {String} label-position 表单域提示文字的位置,left-左侧,top-上方
|
||||
* @property {String Number} label-width 提示文字的宽度,单位rpx(默认90)
|
||||
* @property {Object} label-style lable的样式,对象形式
|
||||
* @property {String} label-align lable的对齐方式
|
||||
* @property {Object} rules 通过ref设置,见官网说明
|
||||
* @property {Array} error-type 错误的提示方式,数组形式,见上方说明(默认['message'])
|
||||
* @example <u-form :model="form" ref="uForm"></u-form>
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'u-form',
|
||||
props: {
|
||||
// 当前form的需要验证字段的集合
|
||||
model: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 验证规则
|
||||
// rules: {
|
||||
// type: [Object, Function, Array],
|
||||
// default() {
|
||||
// return {};
|
||||
// }
|
||||
// },
|
||||
// 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色,
|
||||
// border-bottom-下边框呈现红色,none-无提示
|
||||
errorType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['message', 'toast']
|
||||
}
|
||||
},
|
||||
// 是否显示表单域的下划线边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// label的位置,left-左边,top-上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// label的宽度,单位rpx
|
||||
labelWidth: {
|
||||
type: [String, Number],
|
||||
default: 90
|
||||
},
|
||||
// lable字体的对齐方式
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// lable的样式,对象形式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
uForm: this
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 存储当前form下的所有u-form-item的实例
|
||||
// 不能定义在data中,否则微信小程序会造成循环引用而报错
|
||||
this.fields = [];
|
||||
},
|
||||
methods: {
|
||||
setRules(rules) {
|
||||
this.rules = rules;
|
||||
},
|
||||
// 清空所有u-form-item组件的内容,本质上是调用了u-form-item组件中的resetField()方法
|
||||
resetFields() {
|
||||
this.fields.map(field => {
|
||||
field.resetField();
|
||||
});
|
||||
},
|
||||
// 校验全部数据
|
||||
validate(callback) {
|
||||
return new Promise(resolve => {
|
||||
// 对所有的u-form-item进行校验
|
||||
let valid = true; // 默认通过
|
||||
let count = 0; // 用于标记是否检查完毕
|
||||
let errorArr = []; // 存放错误信息
|
||||
this.fields.map(field => {
|
||||
// 调用每一个u-form-item实例的validation的校验方法
|
||||
field.validation('', error => {
|
||||
// 如果任意一个u-form-item校验不通过,就意味着整个表单不通过
|
||||
if (error) {
|
||||
valid = false;
|
||||
errorArr.push(error);
|
||||
}
|
||||
// 当历遍了所有的u-form-item时,调用promise的then方法
|
||||
if (++count === this.fields.length) {
|
||||
resolve(valid); // 进入promise的then方法
|
||||
// 判断是否设置了toast的提示方式,只提示最前面的表单域的第一个错误信息
|
||||
if(this.errorType.indexOf('none') === -1 && this.errorType.indexOf('toast') >= 0 && errorArr.length) {
|
||||
this.$u.toast(errorArr[0]);
|
||||
}
|
||||
// 调用回调方法
|
||||
if (typeof callback == 'function') callback(valid);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
</style>
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<u-modal v-model="show" :show-cancel-button="true" confirm-text="升级" title="发现新版本" @cancel="cancel" @confirm="confirm">
|
||||
<view class="u-update-content">
|
||||
<rich-text :nodes="content"></rich-text>
|
||||
</view>
|
||||
</u-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
content: `
|
||||
1. 修复badge组件的size参数无效问题<br>
|
||||
2. 新增Modal模态框组件<br>
|
||||
3. 新增压窗屏组件,可以在APP上以弹窗的形式遮盖导航栏和底部tabbar<br>
|
||||
4. 修复键盘组件在微信小程序上遮罩无效的问题
|
||||
`,
|
||||
}
|
||||
},
|
||||
onReady() {
|
||||
this.show = true;
|
||||
},
|
||||
methods: {
|
||||
cancel() {
|
||||
this.closeModal();
|
||||
},
|
||||
confirm() {
|
||||
this.closeModal();
|
||||
},
|
||||
closeModal() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-full-content {
|
||||
background-color: #00C777;
|
||||
}
|
||||
|
||||
.u-update-content {
|
||||
font-size: 26rpx;
|
||||
color: $u-content-color;
|
||||
line-height: 1.7;
|
||||
padding: 30rpx;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<view class="u-gap" :style="[gapStyle]"></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* gap 间隔槽
|
||||
* @description 该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量
|
||||
* @tutorial https://www.uviewui.com/components/gap.html
|
||||
* @property {String} bg-color 背景颜色(默认#f3f4f6)
|
||||
* @property {String Number} height 分割槽高度,单位rpx(默认30)
|
||||
* @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0)
|
||||
* @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0)
|
||||
* @example <u-gap height="80" bg-color="#bbb"></u-gap>
|
||||
*/
|
||||
export default {
|
||||
name: "u-gap",
|
||||
props: {
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'transparent ' // 背景透明
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 30
|
||||
},
|
||||
// 与上一个组件的距离
|
||||
marginTop: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 与下一个组件的距离
|
||||
marginBottom: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
gapStyle() {
|
||||
return {
|
||||
backgroundColor: this.bgColor,
|
||||
height: this.height + 'rpx',
|
||||
marginTop: this.marginTop + 'rpx',
|
||||
marginBottom: this.marginBottom + 'rpx'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
</style>
|
|
@ -0,0 +1,126 @@
|
|||
<template>
|
||||
<view class="u-grid-item" :hover-class="parentData.hoverClass"
|
||||
:hover-stay-time="200" @tap="click" :style="{
|
||||
background: bgColor,
|
||||
width: width,
|
||||
}">
|
||||
<view class="u-grid-item-box" :style="[customStyle]" :class="[parentData.border ? 'u-border-right u-border-bottom' : '']">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* gridItem 提示
|
||||
* @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。搭配u-grid使用
|
||||
* @tutorial https://www.uviewui.com/components/grid.html
|
||||
* @property {String} bg-color 宫格的背景颜色(默认#ffffff)
|
||||
* @property {String Number} index 点击宫格时,返回的值
|
||||
* @property {Object} custom-style 自定义样式,对象形式
|
||||
* @event {Function} click 点击宫格触发
|
||||
* @example <u-grid-item></u-grid-item>
|
||||
*/
|
||||
export default {
|
||||
name: "u-grid-item",
|
||||
props: {
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
// 点击时返回的index
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 自定义样式,对象形式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
padding: '30rpx 0'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
parentData: {
|
||||
hoverClass: '', // 按下去的时候,是否显示背景灰色
|
||||
col: 3, // 父组件划分的宫格数
|
||||
border: true, // 是否显示边框,根据父组件决定
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 父组件的实例
|
||||
this.updateParentData();
|
||||
// this.parent在updateParentData()中定义
|
||||
this.parent.children.push(this);
|
||||
},
|
||||
computed: {
|
||||
// 每个grid-item的宽度
|
||||
width() {
|
||||
return 100 / Number(this.parentData.col) + '%';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取父组件的参数
|
||||
updateParentData() {
|
||||
// 此方法写在mixin中
|
||||
this.getParentData('u-grid');
|
||||
},
|
||||
click() {
|
||||
this.$emit('click', this.index);
|
||||
this.parent && this.parent.click(this.index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-grid-item {
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
|
||||
/* #ifdef MP */
|
||||
position: relative;
|
||||
float: left;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.u-grid-item-hover {
|
||||
background: #f7f7f7 !important;
|
||||
}
|
||||
|
||||
.u-grid-marker-box {
|
||||
position: absolute;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.u-grid-marker-wrap {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.u-grid-item-box {
|
||||
padding: 30rpx 0;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<view class="u-grid" :class="{'u-border-top u-border-left': border}" :style="[gridStyle]"><slot /></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* grid 宫格布局
|
||||
* @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。
|
||||
* @tutorial https://www.uviewui.com/components/grid.html
|
||||
* @property {String Number} col 宫格的列数(默认3)
|
||||
* @property {Boolean} border 是否显示宫格的边框(默认true)
|
||||
* @property {Boolean} hover-class 点击宫格的时候,是否显示按下的灰色背景(默认false)
|
||||
* @event {Function} click 点击宫格触发
|
||||
* @example <u-grid :col="3" @click="click"></u-grid>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-grid',
|
||||
props: {
|
||||
// 分成几列
|
||||
col: {
|
||||
type: [Number, String],
|
||||
default: 3
|
||||
},
|
||||
// 是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 宫格按压时的样式类,"none"为无效果
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'u-hover-class'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
index: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
|
||||
parentData() {
|
||||
if(this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(u-radio)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
|
||||
typeof(child.updateParentData) == 'function' && child.updateParentData();
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
|
||||
this.children = [];
|
||||
},
|
||||
computed: {
|
||||
// 计算父组件的值是否发生变化
|
||||
parentData() {
|
||||
return [this.hoverClass, this.col, this.size, this.border];
|
||||
},
|
||||
// 宫格对齐方式
|
||||
gridStyle() {
|
||||
let style = {};
|
||||
switch(this.align) {
|
||||
case 'left':
|
||||
style.justifyContent = 'flex-start';
|
||||
break;
|
||||
case 'center':
|
||||
style.justifyContent = 'center';
|
||||
break;
|
||||
case 'right':
|
||||
style.justifyContent = 'flex-end';
|
||||
break;
|
||||
default: style.justifyContent = 'flex-start';
|
||||
};
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(index) {
|
||||
this.$emit('click', index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-grid {
|
||||
width: 100%;
|
||||
/* #ifdef MP */
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
/* #endif */
|
||||
|
||||
/* #ifndef MP */
|
||||
@include vue-flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,336 @@
|
|||
<template>
|
||||
<view :style="[customStyle]" class="u-icon" @tap="click" :class="['u-icon--' + labelPos]">
|
||||
<image class="u-icon__img" v-if="isImg" :src="name" :mode="imgMode" :style="[imgStyle]"></image>
|
||||
<text v-else class="u-icon__icon" :class="customClass" :style="[iconStyle]" :hover-class="hoverClass"
|
||||
@touchstart="touchstart">
|
||||
<text v-if="showDecimalIcon" :style="[decimalIconStyle]" :class="decimalIconClass" :hover-class="hoverClass"
|
||||
class="u-icon__decimal">
|
||||
</text>
|
||||
</text>
|
||||
<!-- 这里进行空字符串判断,如果仅仅是v-if="label",可能会出现传递0的时候,结果也无法显示 -->
|
||||
<text v-if="label !== ''" class="u-icon__label" :style="{
|
||||
color: labelColor,
|
||||
fontSize: $u.addUnit(labelSize),
|
||||
marginLeft: labelPos == 'right' ? $u.addUnit(marginLeft) : 0,
|
||||
marginTop: labelPos == 'bottom' ? $u.addUnit(marginTop) : 0,
|
||||
marginRight: labelPos == 'left' ? $u.addUnit(marginRight) : 0,
|
||||
marginBottom: labelPos == 'top' ? $u.addUnit(marginBottom) : 0,
|
||||
}">{{ label }}
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* icon 图标
|
||||
* @description 基于字体的图标集,包含了大多数常见场景的图标。
|
||||
* @tutorial https://www.uviewui.com/components/icon.html
|
||||
* @property {String} name 图标名称,见示例图标集
|
||||
* @property {String} color 图标颜色(默认inherit)
|
||||
* @property {String | Number} size 图标字体大小,单位rpx(默认32)
|
||||
* @property {String | Number} label-size label字体大小,单位rpx(默认28)
|
||||
* @property {String} label 图标右侧的label文字(默认28)
|
||||
* @property {String} label-pos label文字相对于图标的位置,只能right或bottom(默认right)
|
||||
* @property {String} label-color label字体颜色(默认#606266)
|
||||
* @property {Object} custom-style icon的样式,对象形式
|
||||
* @property {String} custom-prefix 自定义字体图标库时,需要写上此值
|
||||
* @property {String | Number} margin-left label在右侧时与图标的距离,单位rpx(默认6)
|
||||
* @property {String | Number} margin-top label在下方时与图标的距离,单位rpx(默认6)
|
||||
* @property {String | Number} margin-bottom label在上方时与图标的距离,单位rpx(默认6)
|
||||
* @property {String | Number} margin-right label在左侧时与图标的距离,单位rpx(默认6)
|
||||
* @property {String} label-pos label相对于图标的位置,只能right或bottom(默认right)
|
||||
* @property {String} index 一个用于区分多个图标的值,点击图标时通过click事件传出
|
||||
* @property {String} hover-class 图标按下去的样式类,用法同uni的view组件的hover-class参数,详情见官网
|
||||
* @property {String} width 显示图片小图标时的宽度
|
||||
* @property {String} height 显示图片小图标时的高度
|
||||
* @property {String} top 图标在垂直方向上的定位
|
||||
* @property {String} top 图标在垂直方向上的定位
|
||||
* @property {String} top 图标在垂直方向上的定位
|
||||
* @property {Boolean} show-decimal-icon 是否为DecimalIcon
|
||||
* @property {String} inactive-color 背景颜色,可接受主题色,仅Decimal时有效
|
||||
* @property {String | Number} percent 显示的百分比,仅Decimal时有效
|
||||
* @event {Function} click 点击图标时触发
|
||||
* @example <u-icon name="photo" color="#2979ff" size="28"></u-icon>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-icon',
|
||||
props: {
|
||||
// 图标类名
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标颜色,可接受主题色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 字体大小,单位rpx
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 'inherit'
|
||||
},
|
||||
// 是否显示粗体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 触摸图标时的类名
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 自定义扩展前缀,方便用户扩展自己的图标库
|
||||
customPrefix: {
|
||||
type: String,
|
||||
default: 'uicon'
|
||||
},
|
||||
// 图标右边或者下面的文字
|
||||
label: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// label的位置,只能右边或者下边
|
||||
labelPos: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// label的大小
|
||||
labelSize: {
|
||||
type: [String, Number],
|
||||
default: '28'
|
||||
},
|
||||
// label的颜色
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// label与图标的距离(横向排列)
|
||||
marginLeft: {
|
||||
type: [String, Number],
|
||||
default: '6'
|
||||
},
|
||||
// label与图标的距离(竖向排列)
|
||||
marginTop: {
|
||||
type: [String, Number],
|
||||
default: '6'
|
||||
},
|
||||
// label与图标的距离(竖向排列)
|
||||
marginRight: {
|
||||
type: [String, Number],
|
||||
default: '6'
|
||||
},
|
||||
// label与图标的距离(竖向排列)
|
||||
marginBottom: {
|
||||
type: [String, Number],
|
||||
default: '6'
|
||||
},
|
||||
// 图片的mode
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'widthFix'
|
||||
},
|
||||
// 自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 用于显示图片小图标时,图片的宽度
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 用于显示图片小图标时,图片的高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 用于解决某些情况下,让图标垂直居中的用途
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否为DecimalIcon
|
||||
showDecimalIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 背景颜色,可接受主题色,仅Decimal时有效
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#ececec'
|
||||
},
|
||||
// 显示的百分比,仅Decimal时有效
|
||||
percent: {
|
||||
type: [Number, String],
|
||||
default: '50'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
customClass() {
|
||||
let classes = []
|
||||
classes.push(this.customPrefix + '-' + this.name)
|
||||
// uView的自定义图标类名为u-iconfont
|
||||
if (this.customPrefix == 'uicon') {
|
||||
classes.push('u-iconfont')
|
||||
} else {
|
||||
classes.push(this.customPrefix)
|
||||
}
|
||||
// 主题色,通过类配置
|
||||
if (this.showDecimalIcon && this.inactiveColor && this.$u.config.type.includes(this.inactiveColor)) {
|
||||
classes.push('u-icon__icon--' + this.inactiveColor)
|
||||
} else if (this.color && this.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)
|
||||
// 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
|
||||
// 故需将其拆成一个字符串的形式,通过空格隔开各个类名
|
||||
//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
|
||||
classes = classes.join(' ')
|
||||
//#endif
|
||||
return classes
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
style = {
|
||||
fontSize: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size),
|
||||
fontWeight: this.bold ? 'bold' : 'normal',
|
||||
// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
|
||||
top: this.$u.addUnit(this.top)
|
||||
}
|
||||
// 非主题色值时,才当作颜色值
|
||||
if (this.showDecimalIcon && this.inactiveColor && !this.$u.config.type.includes(this.inactiveColor)) {
|
||||
style.color = this.inactiveColor
|
||||
} else if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color
|
||||
|
||||
return style
|
||||
},
|
||||
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
|
||||
isImg() {
|
||||
return this.name.indexOf('/') !== -1
|
||||
},
|
||||
imgStyle() {
|
||||
let style = {}
|
||||
// 如果设置width和height属性,则优先使用,否则使用size属性
|
||||
style.width = this.width ? this.$u.addUnit(this.width) : this.$u.addUnit(this.size)
|
||||
style.height = this.height ? this.$u.addUnit(this.height) : this.$u.addUnit(this.size)
|
||||
return style
|
||||
},
|
||||
decimalIconStyle() {
|
||||
let style = {}
|
||||
style = {
|
||||
fontSize: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size),
|
||||
fontWeight: this.bold ? 'bold' : 'normal',
|
||||
// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
|
||||
top: this.$u.addUnit(this.top),
|
||||
width: this.percent + '%'
|
||||
}
|
||||
// 非主题色值时,才当作颜色值
|
||||
if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color
|
||||
return style
|
||||
},
|
||||
decimalIconClass() {
|
||||
let classes = []
|
||||
classes.push(this.customPrefix + '-' + this.name)
|
||||
// uView的自定义图标类名为u-iconfont
|
||||
if (this.customPrefix == 'uicon') {
|
||||
classes.push('u-iconfont')
|
||||
} else {
|
||||
classes.push(this.customPrefix)
|
||||
}
|
||||
// 主题色,通过类配置
|
||||
if (this.color && this.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)
|
||||
else classes.push('u-icon__icon--primary')
|
||||
// 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
|
||||
// 故需将其拆成一个字符串的形式,通过空格隔开各个类名
|
||||
//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
|
||||
classes = classes.join(' ')
|
||||
//#endif
|
||||
return classes
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.$emit('click', this.index)
|
||||
},
|
||||
touchstart() {
|
||||
this.$emit('touchstart', this.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
@import '../../iconfont.css';
|
||||
|
||||
.u-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&--left {
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--top {
|
||||
flex-direction: column-reverse;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: relative;
|
||||
|
||||
&--primary {
|
||||
color: $u-type-primary;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: $u-type-success;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: $u-type-error;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: $u-type-warning;
|
||||
}
|
||||
|
||||
&--info {
|
||||
color: $u-type-info;
|
||||
}
|
||||
}
|
||||
|
||||
&__decimal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__img {
|
||||
height: auto;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
&__label {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,267 @@
|
|||
<template>
|
||||
<view class="u-image" @tap="onClick" :style="[wrapStyle, backgroundStyle]">
|
||||
<image
|
||||
v-if="!isError"
|
||||
:src="src"
|
||||
:mode="mode"
|
||||
@error="onErrorHandler"
|
||||
@load="onLoadHandler"
|
||||
:lazy-load="lazyLoad"
|
||||
class="u-image__image"
|
||||
:show-menu-by-longpress="showMenuByLongpress"
|
||||
:style="{
|
||||
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius)
|
||||
}"
|
||||
></image>
|
||||
<view
|
||||
v-if="showLoading && loading"
|
||||
class="u-image__loading"
|
||||
:style="{
|
||||
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius),
|
||||
backgroundColor: this.bgColor
|
||||
}"
|
||||
>
|
||||
<slot v-if="$slots.loading" name="loading" />
|
||||
<u-icon v-else :name="loadingIcon" :width="width" :height="height"></u-icon>
|
||||
</view>
|
||||
<view
|
||||
v-if="showError && isError && !loading"
|
||||
class="u-image__error"
|
||||
:style="{
|
||||
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius)
|
||||
}"
|
||||
>
|
||||
<slot v-if="$slots.error" name="error" />
|
||||
<u-icon v-else :name="errorIcon" :width="width" :height="height"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Image 图片
|
||||
* @description 此组件为uni-app的image组件的加强版,在继承了原有功能外,还支持淡入动画、加载中、加载失败提示、圆角值和形状等。
|
||||
* @tutorial https://uviewui.com/components/image.html
|
||||
* @property {String} src 图片地址
|
||||
* @property {String} mode 裁剪模式,见官网说明
|
||||
* @property {String | Number} width 宽度,单位任意,如果为数值,则为rpx单位(默认100%)
|
||||
* @property {String | Number} height 高度,单位任意,如果为数值,则为rpx单位(默认 auto)
|
||||
* @property {String} shape 图片形状,circle-圆形,square-方形(默认square)
|
||||
* @property {String | Number} border-radius 圆角值,单位任意,如果为数值,则为rpx单位(默认 0)
|
||||
* @property {Boolean} lazy-load 是否懒加载,仅微信小程序、App、百度小程序、字节跳动小程序有效(默认 true)
|
||||
* @property {Boolean} show-menu-by-longpress 是否开启长按图片显示识别小程序码菜单,仅微信小程序有效(默认 false)
|
||||
* @property {String} loading-icon 加载中的图标,或者小图片(默认 photo)
|
||||
* @property {String} error-icon 加载失败的图标,或者小图片(默认 error-circle)
|
||||
* @property {Boolean} show-loading 是否显示加载中的图标或者自定义的slot(默认 true)
|
||||
* @property {Boolean} show-error 是否显示加载错误的图标或者自定义的slot(默认 true)
|
||||
* @property {Boolean} fade 是否需要淡入效果(默认 true)
|
||||
* @property {String Number} width 传入图片路径时图片的宽度
|
||||
* @property {String Number} height 传入图片路径时图片的高度
|
||||
* @property {Boolean} webp 只支持网络资源,只对微信小程序有效(默认 false)
|
||||
* @property {String | Number} duration 搭配fade参数的过渡时间,单位ms(默认 500)
|
||||
* @event {Function} click 点击图片时触发
|
||||
* @event {Function} error 图片加载失败时触发
|
||||
* @event {Function} load 图片加载成功时触发
|
||||
* @example <u-image width="100%" height="300rpx" :src="src"></u-image>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-image',
|
||||
props: {
|
||||
// 图片地址
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 裁剪模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 宽度,单位任意
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: '100%'
|
||||
},
|
||||
// 高度,单位任意
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 图片形状,circle-圆形,square-方形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
// 圆角,单位任意
|
||||
borderRadius: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否懒加载,微信小程序、App、百度小程序、字节跳动小程序
|
||||
lazyLoad: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 开启长按图片显示识别微信小程序码菜单
|
||||
showMenuByLongpress: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 加载中的图标,或者小图片
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: 'photo'
|
||||
},
|
||||
// 加载失败的图标,或者小图片
|
||||
errorIcon: {
|
||||
type: String,
|
||||
default: 'error-circle'
|
||||
},
|
||||
// 是否显示加载中的图标或者自定义的slot
|
||||
showLoading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示加载错误的图标或者自定义的slot
|
||||
showError: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否需要淡入效果
|
||||
fade: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 只支持网络资源,只对微信小程序有效
|
||||
webp: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 过渡时间,单位ms
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: 500
|
||||
},
|
||||
// 背景颜色,用于深色页面加载图片时,为了和背景色融合
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#f3f4f6'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 图片是否加载错误,如果是,则显示错误占位图
|
||||
isError: false,
|
||||
// 初始化组件时,默认为加载中状态
|
||||
loading: true,
|
||||
// 不透明度,为了实现淡入淡出的效果
|
||||
opacity: 1,
|
||||
// 过渡时间,因为props的值无法修改,故需要一个中间值
|
||||
durationTime: this.duration,
|
||||
// 图片加载完成时,去掉背景颜色,因为如果是png图片,就会显示灰色的背景
|
||||
backgroundStyle: {}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
src: {
|
||||
immediate: true,
|
||||
handler (n) {
|
||||
if(!n) {
|
||||
// 如果传入null或者'',或者false,或者undefined,标记为错误状态
|
||||
this.isError = true;
|
||||
this.loading = false;
|
||||
} else {
|
||||
this.isError = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wrapStyle() {
|
||||
let style = {};
|
||||
// 通过调用addUnit()方法,如果有单位,如百分比,px单位等,直接返回,如果是纯粹的数值,则加上rpx单位
|
||||
style.width = this.$u.addUnit(this.width);
|
||||
style.height = this.$u.addUnit(this.height);
|
||||
// 如果是配置了圆形,设置50%的圆角,否则按照默认的配置值
|
||||
style.borderRadius = this.shape == 'circle' ? '50%' : this.$u.addUnit(this.borderRadius);
|
||||
// 如果设置圆角,必须要有hidden,否则可能圆角无效
|
||||
style.overflow = this.borderRadius > 0 ? 'hidden' : 'visible';
|
||||
if (this.fade) {
|
||||
style.opacity = this.opacity;
|
||||
style.transition = `opacity ${Number(this.durationTime) / 1000}s ease-in-out`;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击图片
|
||||
onClick() {
|
||||
this.$emit('click');
|
||||
},
|
||||
// 图片加载失败
|
||||
onErrorHandler(err) {
|
||||
this.loading = false;
|
||||
this.isError = true;
|
||||
this.$emit('error', err);
|
||||
},
|
||||
// 图片加载完成,标记loading结束
|
||||
onLoadHandler() {
|
||||
this.loading = false;
|
||||
this.isError = false;
|
||||
this.$emit('load');
|
||||
// 如果不需要动画效果,就不执行下方代码,同时移除加载时的背景颜色
|
||||
// 否则无需fade效果时,png图片依然能看到下方的背景色
|
||||
if (!this.fade) return this.removeBgColor();
|
||||
// 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的灰色),再改成1,是为了获得过渡效果
|
||||
this.opacity = 0;
|
||||
// 这里设置为0,是为了图片展示到背景全透明这个过程时间为0,延时之后延时之后重新设置为duration,是为了获得背景透明(灰色)
|
||||
// 到图片展示的过程中的淡入效果
|
||||
this.durationTime = 0;
|
||||
// 延时50ms,否则在浏览器H5,过渡效果无效
|
||||
setTimeout(() => {
|
||||
this.durationTime = this.duration;
|
||||
this.opacity = 1;
|
||||
setTimeout(() => {
|
||||
this.removeBgColor();
|
||||
}, this.durationTime);
|
||||
}, 50);
|
||||
},
|
||||
// 移除图片的背景色
|
||||
removeBgColor() {
|
||||
// 淡入动画过渡完成后,将背景设置为透明色,否则png图片会看到灰色的背景
|
||||
this.backgroundStyle = {
|
||||
backgroundColor: 'transparent'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../libs/css/style.components.scss';
|
||||
|
||||
.u-image {
|
||||
position: relative;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__loading,
|
||||
&__error {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: $u-bg-color;
|
||||
color: $u-tips-color;
|
||||
font-size: 46rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,89 @@
|
|||
<template>
|
||||
<!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸,所以在外面套一个"壳" -->
|
||||
<view>
|
||||
<view class="u-index-anchor-wrapper" :id="$u.guid()" :style="[wrapperStyle]">
|
||||
<view class="u-index-anchor " :class="[active ? 'u-index-anchor--active' : '']" :style="[customAnchorStyle]">
|
||||
<slot v-if="useSlot" />
|
||||
<block v-else>
|
||||
<text>{{ index }}</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* indexAnchor 索引列表锚点
|
||||
* @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用
|
||||
* @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
|
||||
* @property {Boolean} use-slot 是否使用自定义内容的插槽(默认false)
|
||||
* @property {String Number} index 索引字符,如果定义了use-slot,此参数自动失效
|
||||
* @property {Object} custStyle 自定义样式,对象形式,如"{color: 'red'}"
|
||||
* @event {Function} default 锚点位置显示内容,默认为索引字符
|
||||
* @example <u-index-anchor :index="item" />
|
||||
*/
|
||||
export default {
|
||||
name: "u-index-anchor",
|
||||
props: {
|
||||
useSlot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
index: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
wrapperStyle: {},
|
||||
anchorStyle: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false;
|
||||
},
|
||||
mounted() {
|
||||
this.parent = this.$u.$parent.call(this, 'u-index-list');
|
||||
if(this.parent) {
|
||||
this.parent.children.push(this);
|
||||
this.parent.updateData();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
customAnchorStyle() {
|
||||
return Object.assign(this.anchorStyle, this.customStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-index-anchor {
|
||||
box-sizing: border-box;
|
||||
padding: 14rpx 24rpx;
|
||||
color: #606266;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.2;
|
||||
background-color: rgb(245, 245, 245);
|
||||
}
|
||||
|
||||
.u-index-anchor--active {
|
||||
right: 0;
|
||||
left: 0;
|
||||
color: #2979ff;
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,315 @@
|
|||
<template>
|
||||
<!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸,所以在外面套一个"壳" -->
|
||||
<view>
|
||||
<view class="u-index-bar">
|
||||
<slot />
|
||||
<view v-if="showSidebar" class="u-index-bar__sidebar" @touchstart.stop.prevent="onTouchMove" @touchmove.stop.prevent="onTouchMove"
|
||||
@touchend.stop.prevent="onTouchStop" @touchcancel.stop.prevent="onTouchStop">
|
||||
<view v-for="(item, index) in indexList" :key="index" class="u-index-bar__index" :style="{zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : ''}"
|
||||
:data-index="index">
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-indexed-list-alert" v-if="touchmove && indexList[touchmoveIndex]" :style="{
|
||||
zIndex: alertZIndex
|
||||
}">
|
||||
<text>{{indexList[touchmoveIndex]}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var indexList = function() {
|
||||
var indexList = [];
|
||||
var charCodeOfA = 'A'.charCodeAt(0);
|
||||
for (var i = 0; i < 26; i++) {
|
||||
indexList.push(String.fromCharCode(charCodeOfA + i));
|
||||
}
|
||||
return indexList;
|
||||
};
|
||||
|
||||
/**
|
||||
* indexList 索引列表
|
||||
* @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用
|
||||
* @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
|
||||
* @property {Number String} scroll-top 当前滚动高度,自定义组件无法获得滚动条事件,所以依赖接入方传入
|
||||
* @property {Array} index-list 索引字符列表,数组(默认A-Z)
|
||||
* @property {Number String} z-index 锚点吸顶时的层级(默认965)
|
||||
* @property {Boolean} sticky 是否开启锚点自动吸顶(默认true)
|
||||
* @property {Number String} offset-top 锚点自动吸顶时与顶部的距离(默认0)
|
||||
* @property {String} highlight-color 锚点和右边索引字符高亮颜色(默认#2979ff)
|
||||
* @event {Function} select 选中右边索引字符时触发
|
||||
* @example <u-index-list :scrollTop="scrollTop"></u-index-list>
|
||||
*/
|
||||
export default {
|
||||
name: "u-index-list",
|
||||
props: {
|
||||
sticky: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
scrollTop: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
offsetTop: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
indexList: {
|
||||
type: Array,
|
||||
default () {
|
||||
return indexList()
|
||||
}
|
||||
},
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// #ifdef H5
|
||||
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44;
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0;
|
||||
// #endif
|
||||
// 只能在created生命周期定义children,如果在data定义,会因为循环引用而报错
|
||||
this.children = [];
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeAnchorIndex: 0,
|
||||
showSidebar: true,
|
||||
// children: [],
|
||||
touchmove: false,
|
||||
touchmoveIndex: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
scrollTop() {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 弹出toast的z-index值
|
||||
alertZIndex() {
|
||||
return this.$u.zIndex.toast;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData() {
|
||||
this.timer && clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
this.showSidebar = !!this.children.length;
|
||||
this.setRect().then(() => {
|
||||
this.onScroll();
|
||||
});
|
||||
}, 0);
|
||||
},
|
||||
setRect() {
|
||||
return Promise.all([
|
||||
this.setAnchorsRect(),
|
||||
this.setListRect(),
|
||||
this.setSiderbarRect()
|
||||
]);
|
||||
},
|
||||
setAnchorsRect() {
|
||||
return Promise.all(this.children.map((anchor, index) => anchor
|
||||
.$uGetRect('.u-index-anchor-wrapper')
|
||||
.then((rect) => {
|
||||
Object.assign(anchor, {
|
||||
height: rect.height,
|
||||
top: rect.top
|
||||
});
|
||||
})));
|
||||
},
|
||||
setListRect() {
|
||||
return this.$uGetRect('.u-index-bar').then((rect) => {
|
||||
Object.assign(this, {
|
||||
height: rect.height,
|
||||
top: rect.top + this.scrollTop
|
||||
});
|
||||
});
|
||||
},
|
||||
setSiderbarRect() {
|
||||
return this.$uGetRect('.u-index-bar__sidebar').then(rect => {
|
||||
this.sidebar = {
|
||||
height: rect.height,
|
||||
top: rect.top
|
||||
};
|
||||
});
|
||||
},
|
||||
getActiveAnchorIndex() {
|
||||
const {
|
||||
children
|
||||
} = this;
|
||||
const {
|
||||
sticky
|
||||
} = this;
|
||||
for (let i = this.children.length - 1; i >= 0; i--) {
|
||||
const preAnchorHeight = i > 0 ? children[i - 1].height : 0;
|
||||
const reachTop = sticky ? preAnchorHeight : 0;
|
||||
if (reachTop >= children[i].top) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
onScroll() {
|
||||
const {
|
||||
children = []
|
||||
} = this;
|
||||
if (!children.length) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
sticky,
|
||||
stickyOffsetTop,
|
||||
zIndex,
|
||||
scrollTop,
|
||||
activeColor
|
||||
} = this;
|
||||
const active = this.getActiveAnchorIndex();
|
||||
this.activeAnchorIndex = active;
|
||||
if (sticky) {
|
||||
let isActiveAnchorSticky = false;
|
||||
if (active !== -1) {
|
||||
isActiveAnchorSticky =
|
||||
children[active].top <= 0;
|
||||
}
|
||||
children.forEach((item, index) => {
|
||||
if (index === active) {
|
||||
let wrapperStyle = '';
|
||||
let anchorStyle = {
|
||||
color: `${activeColor}`
|
||||
};
|
||||
if (isActiveAnchorSticky) {
|
||||
wrapperStyle = {
|
||||
height: `${children[index].height}px`
|
||||
};
|
||||
anchorStyle = {
|
||||
position: 'fixed',
|
||||
top: `${stickyOffsetTop}px`,
|
||||
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
|
||||
color: `${activeColor}`
|
||||
};
|
||||
}
|
||||
item.active = active;
|
||||
item.wrapperStyle = wrapperStyle;
|
||||
item.anchorStyle = anchorStyle;
|
||||
} else if (index === active - 1) {
|
||||
const currentAnchor = children[index];
|
||||
const currentOffsetTop = currentAnchor.top;
|
||||
const targetOffsetTop = index === children.length - 1 ?
|
||||
this.top :
|
||||
children[index + 1].top;
|
||||
const parentOffsetHeight = targetOffsetTop - currentOffsetTop;
|
||||
const translateY = parentOffsetHeight - currentAnchor.height;
|
||||
const anchorStyle = {
|
||||
position: 'relative',
|
||||
transform: `translate3d(0, ${translateY}px, 0)`,
|
||||
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
|
||||
color: `${activeColor}`
|
||||
};
|
||||
item.active = active;
|
||||
item.anchorStyle = anchorStyle;
|
||||
} else {
|
||||
item.active = false;
|
||||
item.anchorStyle = '';
|
||||
item.wrapperStyle = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onTouchMove(event) {
|
||||
this.touchmove = true;
|
||||
const sidebarLength = this.children.length;
|
||||
const touch = event.touches[0];
|
||||
const itemHeight = this.sidebar.height / sidebarLength;
|
||||
let clientY = 0;
|
||||
clientY = touch.clientY;
|
||||
let index = Math.floor((clientY - this.sidebar.top) / itemHeight);
|
||||
if (index < 0) {
|
||||
index = 0;
|
||||
} else if (index > sidebarLength - 1) {
|
||||
index = sidebarLength - 1;
|
||||
}
|
||||
this.touchmoveIndex = index;
|
||||
this.scrollToAnchor(index);
|
||||
},
|
||||
onTouchStop() {
|
||||
this.touchmove = false;
|
||||
this.scrollToAnchorIndex = null;
|
||||
},
|
||||
scrollToAnchor(index) {
|
||||
if (this.scrollToAnchorIndex === index) {
|
||||
return;
|
||||
}
|
||||
this.scrollToAnchorIndex = index;
|
||||
const anchor = this.children.find((item) => item.index === this.indexList[index]);
|
||||
if (anchor) {
|
||||
this.$emit('select', anchor.index);
|
||||
uni.pageScrollTo({
|
||||
duration: 0,
|
||||
scrollTop: anchor.top + this.scrollTop
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-index-bar {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.u-index-bar__sidebar {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
@include vue-flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
transform: translateY(-50%);
|
||||
user-select: none;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.u-index-bar__index {
|
||||
font-weight: 500;
|
||||
padding: 8rpx 18rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1
|
||||
}
|
||||
|
||||
.u-indexed-list-alert {
|
||||
position: fixed;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
right: 90rpx;
|
||||
top: 50%;
|
||||
margin-top: -60rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 50rpx;
|
||||
color: #fff;
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
z-index: 9999999;
|
||||
}
|
||||
|
||||
.u-indexed-list-alert text {
|
||||
line-height: 50rpx;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,387 @@
|
|||
<template>
|
||||
<view
|
||||
class="u-input"
|
||||
:class="{
|
||||
'u-input--border': border,
|
||||
'u-input--error': validateState
|
||||
}"
|
||||
:style="{
|
||||
padding: `0 ${border ? 20 : 0}rpx`,
|
||||
borderColor: borderColor,
|
||||
textAlign: inputAlign
|
||||
}"
|
||||
@tap.stop="inputClick"
|
||||
>
|
||||
<textarea
|
||||
v-if="type == 'textarea'"
|
||||
class="u-input__input u-input__textarea"
|
||||
:style="[getStyle]"
|
||||
:value="defaultValue"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled"
|
||||
:maxlength="inputMaxlength"
|
||||
:fixed="fixed"
|
||||
:focus="focus"
|
||||
:autoHeight="autoHeight"
|
||||
:selection-end="uSelectionEnd"
|
||||
:selection-start="uSelectionStart"
|
||||
:cursor-spacing="getCursorSpacing"
|
||||
:show-confirm-bar="showConfirmbar"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@focus="onFocus"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
class="u-input__input"
|
||||
:type="type == 'password' ? 'text' : type"
|
||||
:style="[getStyle]"
|
||||
:value="defaultValue"
|
||||
:password="type == 'password' && !showPassword"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled || type === 'select'"
|
||||
:maxlength="inputMaxlength"
|
||||
:focus="focus"
|
||||
:confirmType="confirmType"
|
||||
:cursor-spacing="getCursorSpacing"
|
||||
:selection-end="uSelectionEnd"
|
||||
:selection-start="uSelectionStart"
|
||||
:show-confirm-bar="showConfirmbar"
|
||||
@focus="onFocus"
|
||||
@blur="handleBlur"
|
||||
@input="handleInput"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
<view class="u-input__right-icon u-flex">
|
||||
<view class="u-input__right-icon__clear u-input__right-icon__item" @tap="onClear" v-if="clearable && value != '' && focused">
|
||||
<u-icon size="32" name="close-circle-fill" color="#c0c4cc"/>
|
||||
</view>
|
||||
<view class="u-input__right-icon__clear u-input__right-icon__item" v-if="passwordIcon && type == 'password'">
|
||||
<u-icon size="32" :name="!showPassword ? 'eye' : 'eye-fill'" color="#c0c4cc" @click="showPassword = !showPassword"/>
|
||||
</view>
|
||||
<view class="u-input__right-icon--select u-input__right-icon__item" v-if="type == 'select'" :class="{
|
||||
'u-input__right-icon--select--reverse': selectOpen
|
||||
}">
|
||||
<u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/util/emitter.js';
|
||||
|
||||
/**
|
||||
* input 输入框
|
||||
* @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
|
||||
* @tutorial http://uviewui.com/components/input.html
|
||||
* @property {String} type 模式选择,见官网说明
|
||||
* @property {Boolean} clearable 是否显示右侧的清除图标(默认true)
|
||||
* @property {} v-model 用于双向绑定输入框的值
|
||||
* @property {String} input-align 输入框文字的对齐方式(默认left)
|
||||
* @property {String} placeholder placeholder显示值(默认 '请输入内容')
|
||||
* @property {Boolean} disabled 是否禁用输入框(默认false)
|
||||
* @property {String Number} maxlength 输入框的最大可输入长度(默认140)
|
||||
* @property {String Number} selection-start 光标起始位置,自动聚焦时有效,需与selection-end搭配使用(默认-1)
|
||||
* @property {String Number} maxlength 光标结束位置,自动聚焦时有效,需与selection-start搭配使用(默认-1)
|
||||
* @property {String Number} cursor-spacing 指定光标与键盘的距离,单位px(默认0)
|
||||
* @property {String} placeholderStyle placeholder的样式,字符串形式,如"color: red;"(默认 "color: #c0c4cc;")
|
||||
* @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type为text时生效(默认done)
|
||||
* @property {Object} custom-style 自定义输入框的样式,对象形式
|
||||
* @property {Boolean} focus 是否自动获得焦点(默认false)
|
||||
* @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false)
|
||||
* @property {Boolean} password-icon type为password时,是否显示右侧的密码查看图标(默认true)
|
||||
* @property {Boolean} border 是否显示边框(默认false)
|
||||
* @property {String} border-color 输入框的边框颜色(默认#dcdfe6)
|
||||
* @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true)
|
||||
* @property {String Number} height 高度,单位rpx(text类型时为70,textarea时为100)
|
||||
* @example <u-input v-model="value" :type="type" :border="border" />
|
||||
*/
|
||||
export default {
|
||||
name: 'u-input',
|
||||
mixins: [Emitter],
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 输入框的类型,textarea,text,number
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
inputAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入内容'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxlength: {
|
||||
type: [Number, String],
|
||||
default: 140
|
||||
},
|
||||
placeholderStyle: {
|
||||
type: String,
|
||||
default: 'color: #c0c4cc;'
|
||||
},
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'done'
|
||||
},
|
||||
// 输入框的自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动获得焦点
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 密码类型时,是否显示右侧的密码图标
|
||||
passwordIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// input|textarea是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 输入框的边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#dcdfe6'
|
||||
},
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// type=select时,旋转右侧的图标,标识当前处于打开还是关闭select的状态
|
||||
// open-打开,close-关闭
|
||||
selectOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 高度,单位rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否可清空
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 指定光标与键盘的距离,单位 px
|
||||
cursorSpacing: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 光标起始位置,自动聚焦时有效,需与selection-end搭配使用
|
||||
selectionStart: {
|
||||
type: [Number, String],
|
||||
default: -1
|
||||
},
|
||||
// 光标结束位置,自动聚焦时有效,需与selection-start搭配使用
|
||||
selectionEnd: {
|
||||
type: [Number, String],
|
||||
default: -1
|
||||
},
|
||||
// 是否自动去除两端的空格
|
||||
trim: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示键盘上方带有”完成“按钮那一栏
|
||||
showConfirmbar:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultValue: this.value,
|
||||
inputHeight: 70, // input的高度
|
||||
textareaHeight: 100, // textarea的高度
|
||||
validateState: false, // 当前input的验证状态,用于错误时,边框是否改为红色
|
||||
focused: false, // 当前是否处于获得焦点的状态
|
||||
showPassword: false, // 是否预览密码
|
||||
lastValue: '', // 用于头条小程序,判断@input中,前后的值是否发生了变化,因为头条中文下,按下键没有输入内容,也会触发@input时间
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(nVal, oVal) {
|
||||
this.defaultValue = nVal;
|
||||
// 当值发生变化,且为select类型时(此时input被设置为disabled,不会触发@input事件),模拟触发@input事件
|
||||
if(nVal != oVal && this.type == 'select') this.handleInput({
|
||||
detail: {
|
||||
value: nVal
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
|
||||
inputMaxlength() {
|
||||
return Number(this.maxlength);
|
||||
},
|
||||
getStyle() {
|
||||
let style = {};
|
||||
// 如果没有自定义高度,就根据type为input还是textare来分配一个默认的高度
|
||||
style.minHeight = this.height ? this.height + 'rpx' : this.type == 'textarea' ?
|
||||
this.textareaHeight + 'rpx' : this.inputHeight + 'rpx';
|
||||
style = Object.assign(style, this.customStyle);
|
||||
return style;
|
||||
},
|
||||
//
|
||||
getCursorSpacing() {
|
||||
return Number(this.cursorSpacing);
|
||||
},
|
||||
// 光标起始位置
|
||||
uSelectionStart() {
|
||||
return String(this.selectionStart);
|
||||
},
|
||||
// 光标结束位置
|
||||
uSelectionEnd() {
|
||||
return String(this.selectionEnd);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 监听u-form-item发出的错误事件,将输入框边框变红色
|
||||
this.$on('on-form-item-error', this.onFormItemError);
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* change 事件
|
||||
* @param event
|
||||
*/
|
||||
handleInput(event) {
|
||||
let value = event.detail.value;
|
||||
// 判断是否去除空格
|
||||
if(this.trim) value = this.$u.trim(value);
|
||||
// vue 原生的方法 return 出去
|
||||
this.$emit('input', value);
|
||||
// 当前model 赋值
|
||||
this.defaultValue = value;
|
||||
// 过一个生命周期再发送事件给u-form-item,否则this.$emit('input')更新了父组件的值,但是微信小程序上
|
||||
// 尚未更新到u-form-item,导致获取的值为空,从而校验混论
|
||||
// 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱
|
||||
setTimeout(() => {
|
||||
// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
|
||||
// #ifdef MP-TOUTIAO
|
||||
if(this.$u.trim(value) == this.lastValue) return ;
|
||||
this.lastValue = value;
|
||||
// #endif
|
||||
// 将当前的值发送到 u-form-item 进行校验
|
||||
this.dispatch('u-form-item', 'on-form-change', value);
|
||||
}, 40)
|
||||
},
|
||||
/**
|
||||
* blur 事件
|
||||
* @param event
|
||||
*/
|
||||
handleBlur(event) {
|
||||
// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
|
||||
// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
|
||||
setTimeout(() => {
|
||||
this.focused = false;
|
||||
}, 100)
|
||||
// vue 原生的方法 return 出去
|
||||
this.$emit('blur', event.detail.value);
|
||||
setTimeout(() => {
|
||||
// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
|
||||
// #ifdef MP-TOUTIAO
|
||||
if(this.$u.trim(value) == this.lastValue) return ;
|
||||
this.lastValue = value;
|
||||
// #endif
|
||||
// 将当前的值发送到 u-form-item 进行校验
|
||||
this.dispatch('u-form-item', 'on-form-blur', event.detail.value);
|
||||
}, 40)
|
||||
},
|
||||
onFormItemError(status) {
|
||||
this.validateState = status;
|
||||
},
|
||||
onFocus(event) {
|
||||
this.focused = true;
|
||||
this.$emit('focus');
|
||||
},
|
||||
onConfirm(e) {
|
||||
this.$emit('confirm', e.detail.value);
|
||||
},
|
||||
onClear(event) {
|
||||
this.$emit('input', '');
|
||||
},
|
||||
inputClick() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-input {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
@include vue-flex;
|
||||
|
||||
&__input {
|
||||
//height: $u-form-item-height;
|
||||
font-size: 28rpx;
|
||||
color: $u-main-color;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__textarea {
|
||||
width: auto;
|
||||
font-size: 28rpx;
|
||||
color: $u-main-color;
|
||||
padding: 10rpx 0;
|
||||
line-height: normal;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&--border {
|
||||
border-radius: 6rpx;
|
||||
border-radius: 4px;
|
||||
border: 1px solid $u-form-item-border-color;
|
||||
}
|
||||
|
||||
&--error {
|
||||
border-color: $u-type-error!important;
|
||||
}
|
||||
|
||||
&__right-icon {
|
||||
|
||||
&__item {
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
&--select {
|
||||
transition: transform .4s;
|
||||
|
||||
&--reverse {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,217 @@
|
|||
<template>
|
||||
<u-popup class="" :mask="mask" :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :zIndex="uZIndex">
|
||||
<slot />
|
||||
<view class="u-tooltip" v-if="tooltip">
|
||||
<view class="u-tooltip-item u-tooltip-cancel" hover-class="u-tooltip-cancel-hover" @tap="onCancel">
|
||||
{{cancelBtn ? cancelText : ''}}
|
||||
</view>
|
||||
<view v-if="showTips" class="u-tooltip-item u-tooltip-tips">
|
||||
{{tips ? tips : mode == 'number' ? '数字键盘' : mode == 'card' ? '身份证键盘' : '车牌号键盘'}}
|
||||
</view>
|
||||
<view v-if="confirmBtn" @tap="onConfirm" class="u-tooltip-item u-tooltips-submit" hover-class="u-tooltips-submit-hover">
|
||||
{{confirmBtn ? confirmText : ''}}
|
||||
</view>
|
||||
</view>
|
||||
<block v-if="mode == 'number' || mode == 'card'">
|
||||
<u-number-keyboard :random="random" @backspace="backspace" @change="change" :mode="mode" :dotEnabled="dotEnabled"></u-number-keyboard>
|
||||
</block>
|
||||
<block v-else>
|
||||
<u-car-keyboard :random="random" @backspace="backspace" @change="change"></u-car-keyboard>
|
||||
</block>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* keyboard 键盘
|
||||
* @description 此为uViw自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3中模式,都有可以打乱按键顺序的选项。
|
||||
* @tutorial https://www.uviewui.com/components/keyboard.html
|
||||
* @property {String} mode 键盘类型,见官网基本使用的说明(默认number)
|
||||
* @property {Boolean} dot-enabled 是否显示"."按键,只在mode=number时有效(默认true)
|
||||
* @property {Boolean} tooltip 是否显示键盘顶部工具条(默认true)
|
||||
* @property {String} tips 工具条中间的提示文字,见上方基本使用的说明,如不需要,请传""空字符
|
||||
* @property {Boolean} cancel-btn 是否显示工具条左边的"取消"按钮(默认true)
|
||||
* @property {Boolean} confirm-btn 是否显示工具条右边的"完成"按钮(默认true)
|
||||
* @property {Boolean} mask 是否显示遮罩(默认true)
|
||||
* @property {String} confirm-text 确认按钮的文字
|
||||
* @property {String} cancel-text 取消按钮的文字
|
||||
* @property {Number String} z-index 弹出键盘的z-index值(默认1075)
|
||||
* @property {Boolean} random 是否打乱键盘按键的顺序(默认false)
|
||||
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
|
||||
* @property {Boolean} mask-close-able 是否允许点击遮罩收起键盘(默认true)
|
||||
* @event {Function} change 按键被点击(不包含退格键被点击)
|
||||
* @event {Function} cancel 键盘顶部工具条左边的"取消"按钮被点击
|
||||
* @event {Function} confirm 键盘顶部工具条右边的"完成"按钮被点击
|
||||
* @event {Function} backspace 键盘退格键被点击
|
||||
* @example <u-keyboard mode="number" v-model="show"></u-keyboard>
|
||||
*/
|
||||
export default {
|
||||
name: "u-keyboard",
|
||||
props: {
|
||||
// 键盘的类型,number-数字键盘,card-身份证键盘,car-车牌号键盘
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'number'
|
||||
},
|
||||
// 是否显示键盘的"."符号
|
||||
dotEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示顶部工具条
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示工具条中间的提示
|
||||
showTips: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 工具条中间的提示文字
|
||||
tips: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示工具条左边的"取消"按钮
|
||||
cancelBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示工具条右边的"完成"按钮
|
||||
confirmBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否打乱键盘按键的顺序
|
||||
random: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许通过点击遮罩关闭键盘
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 通过双向绑定控制键盘的弹出与收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示遮罩,某些时候数字键盘时,用户希望看到自己的数值,所以可能不想要遮罩
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// z-index值
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 取消按钮的文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 确认按钮的文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
//show: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change(e) {
|
||||
this.$emit('change', e);
|
||||
},
|
||||
// 键盘关闭
|
||||
popupClose() {
|
||||
// 通过发送input这个特殊的事件名,可以修改父组件传给props的value的变量,也即双向绑定
|
||||
this.$emit('input', false);
|
||||
},
|
||||
// 输入完成
|
||||
onConfirm() {
|
||||
this.popupClose();
|
||||
this.$emit('confirm');
|
||||
},
|
||||
// 取消输入
|
||||
onCancel() {
|
||||
this.popupClose();
|
||||
this.$emit('cancel');
|
||||
},
|
||||
// 退格键
|
||||
backspace() {
|
||||
this.$emit('backspace');
|
||||
},
|
||||
// 关闭键盘
|
||||
// close() {
|
||||
// this.show = false;
|
||||
// },
|
||||
// // 打开键盘
|
||||
// open() {
|
||||
// this.show = true;
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-keyboard {
|
||||
position: relative;
|
||||
z-index: 1003;
|
||||
}
|
||||
|
||||
.u-tooltip {
|
||||
@include vue-flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.u-tooltip-item {
|
||||
color: #333333;
|
||||
flex: 0 0 33.333333%;
|
||||
text-align: center;
|
||||
padding: 20rpx 10rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.u-tooltips-submit {
|
||||
text-align: right;
|
||||
flex-grow: 1;
|
||||
flex-wrap: 0;
|
||||
padding-right: 40rpx;
|
||||
color: $u-type-primary;
|
||||
}
|
||||
|
||||
.u-tooltip-cancel {
|
||||
text-align: left;
|
||||
flex-grow: 1;
|
||||
flex-wrap: 0;
|
||||
padding-left: 40rpx;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.u-tooltips-submit-hover {
|
||||
color: $u-type-success;
|
||||
}
|
||||
|
||||
.u-tooltip-cancel-hover {
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,244 @@
|
|||
<template>
|
||||
<view class="u-wrap" :style="{
|
||||
opacity: Number(opacity),
|
||||
borderRadius: borderRadius + 'rpx',
|
||||
// 因为time值需要改变,所以不直接用duration值(不能改变父组件prop传过来的值)
|
||||
transition: `opacity ${time / 1000}s ease-in-out`
|
||||
}"
|
||||
:class="'u-lazy-item-' + elIndex">
|
||||
<view :class="'u-lazy-item-' + elIndex">
|
||||
<image :style="{borderRadius: borderRadius + 'rpx', height: imgHeight}" v-if="!isError" class="u-lazy-item"
|
||||
:src="isShow ? image : loadingImg" :mode="imgMode" @load="imgLoaded" @error="loadError" @tap="clickImg"></image>
|
||||
<image :style="{borderRadius: borderRadius + 'rpx', height: imgHeight}" class="u-lazy-item error" v-else :src="errorImg"
|
||||
:mode="imgMode" @load="errorImgLoaded" @tap="clickImg"></image>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* lazyLoad 懒加载
|
||||
* @description 懒加载使用的场景为:页面有很多图片时,APP会同时加载所有的图片,导致页面卡顿,各个位置的图片出现前后不一致等.
|
||||
* @tutorial https://www.uviewui.com/components/lazyLoad.html
|
||||
* @property {String Number} index 用户自定义值,在事件触发时回调,用以区分是哪个图片
|
||||
* @property {String} image 图片路径
|
||||
* @property {String} loading-img 预加载时的占位图
|
||||
* @property {String} error-img 图片加载出错时的占位图
|
||||
* @property {String} threshold 触发加载时的位置,见上方说明,单位 rpx(默认300)
|
||||
* @property {String Number} duration 图片加载成功时,淡入淡出时间,单位ms(默认)
|
||||
* @property {String} effect 图片加载成功时,淡入淡出的css动画效果(默认ease-in-out)
|
||||
* @property {Boolean} is-effect 图片加载成功时,是否启用淡入淡出效果(默认true)
|
||||
* @property {String Number} border-radius 图片圆角值,单位rpx(默认0)
|
||||
* @property {String Number} height 图片高度,注意:实际高度可能受img-mode参数影响(默认450)
|
||||
* @property {String Number} mg-mode 图片的裁剪模式,详见image组件裁剪模式(默认widthFix)
|
||||
* @event {Function} click 点击图片时触发
|
||||
* @event {Function} load 图片加载成功时触发
|
||||
* @event {Function} error 图片加载失败时触发
|
||||
* @example <u-lazy-load :image="image" :loading-img="loadingImg" :error-img="errorImg"></u-lazy-load>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-lazy-load',
|
||||
props: {
|
||||
index: {
|
||||
type: [Number, String]
|
||||
},
|
||||
// 要显示的图片
|
||||
image: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图片裁剪模式
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'widthFix'
|
||||
},
|
||||
// 占位图片路径
|
||||
loadingImg: {
|
||||
type: String,
|
||||
default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM0QjNBQjkyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM0QjNBQkEyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzRCM0FCNzJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzRCM0FCODJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtRHfPcAAAAzUExURZWVldfX18PDw62trZubm9zc3Li4uKGhoebm5tLS0uHh4c3Nzaenp729vcjIyLKysuvr6141L40AAAcXSURBVHja7NzZlqpGAEBR5lG0//9rIw7IJKJi4or7PGTdtN10wr5SVAEGf/qqArsAiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAg+nmQFMi5Jis+sIniED23jSzIgLTtg2D//iYme/8QBM/9lQ+CAEhbNLM3N9hEHAThX7GPCiBfAxK1b51kD+R7QMLjXg7iCsgWIPUh7pfVozG791oeBPngm48G583uW5GkBvI+SBaM2xXDn1oqum423bX/mgF5FySc2cv93Voug9TdZotsggnkBZB2NzbhrSY5HnoG07jei8dvzsJB/c3W60SALILE46+WCztsbhPR7R2VJq0ukEcT49nyy8QhaKcRa3fYHZD4+ufqOJAcgDz8/59vtw1I3QP5K6JsOG0vm3hce4I8LQp/BaRZGJC3AAn7IKOKXbC+7EdA5vdmmVwOLksgRThqOqiH4XEGsht+peoPUE8U/jJIO5OLH4GEwUslV5G0PTBG5Uiw/Y2jyigO3l9HAHKv9PYb82LloH74dZBoBUgar+l48NsNvtD0fkez9iwrAvIYZDRCl+Xs149Hm/KZmQ+QjUCiO1ei4ru7EsgnQYrkznlQb7thCuRfAzlOAPN72427P4VA/i2Q/DKT/Ls/VR8fvIBsDZIuz7TPF6TCbnk4GJkB2RokejTjuE7/unlgCuSTIO0Cy+Plp6vDfnQlBchy8QtjSHVd3EgmK1bHLm+H6+nXYbz2DuQRSPnqoL7vvq0u70on4zvxgCyWD3b9UyDVdW24PaWaiGTnFZJwPIQAebDpIKheBIm7n124ZthMJipAlkqHO+IZkP1tbfzOJark/A7MgKyvvl60fRqkvXfhuow+t9+q00+0/yyBrK8ZngOtBzldhw2X9tvpNGty0gvkmbPeJ0Cy/r09s/stbmfo0yMWkEdjevgKyOn2t2pxv7UXoibTdCDLje9/Ww1ymqzn87dbp92242ZmMRjI8hASvwKSLq4udqN6ksw8nxXN3tszD9L8Gkg+2mFrQYql5az4tvFj5xOx4VwnSdeBtGdyPwUytxK77pBVlNHdO7OK3rh/eTPUvdutT3fO52tuHMqD4N7llv8pyOQQ//w19YVDfX27+Sfuby9/6nau4pdA8vEdOZuChEH/quHt0Jg+IRJ/5+PrHwKZXfjbDiS73Zo7mu5UkzX7uTsXe0e/7nC3ePf1O69+BUg2XDfZCqSqOu7rGVf8cHBe8zhC2b61dtUHXv0OkGo6ZL4JkpbRYXdUaFevivx2M/1GIOctNh949TtAoumQ+TpIHMX54CJu+8BDd8FkE5BqcZh/59XvAClmTvKfB0nDqIlHo3T70SftyW1eX9dXtgQJqs1f/Q6QaOa/7wmQKtxH8eiGoCRuovODIO3VxOMmruZbHrLyD7z6DSDtGyT7ew1kf9hNn07c986JTovzzem0Id9wUG+Vk/IDr34DSNR7huZJkMFT6vEhqrPx/j5cnlZML8N6/PAzh9Y99Flm5Yde/c9BquDOkvkKkMP58dA4qi9vivE8JOvGz/j8FokfPpr288+pH2ZPOZrLmeGD+7KOh6dqYWJ48ki7yUg0tz0go/fv/LLddfV3sgOLJyaGPY/zrSlh1a36Arkzoue9CyG35ze6E6/dzO2Ga0EGHqdRJIkfn9/8OEjTW8Vq91ZWh39FeehWA7Nu9ft8CpUEk1WWOyDF0OPyEU2Pnzf/bZC0P6IPzmAvu7KauQBVrgKpJ0tG2arHzX8e5Pb3PezNs/PrX+3JMyCLn9XXf37tPFHvt09WfCDDjx+yyn1/p1V11j7GnB/q3leLuVva79S/tzed+db08YpF4uOZtmz/9oXWMq6BCAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiAALELvqt/BBgACqVeUBXxcCkAAAAASUVORK5CYII='
|
||||
},
|
||||
// 加载失败的错误占位图
|
||||
errorImg: {
|
||||
type: String,
|
||||
default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODdDMjhENDYyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODdDMjhENDcyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4N0MyOEQ0NDJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4N0MyOEQ0NTJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PhLwhikAAAAzUExURZWVldfX162trcPDw5ubm7i4uNzc3Obm5s3NzaGhoeHh4cjIyKenp9LS0r29vbKysuvr67sDMEkAAAlpSURBVHja7NzpYqMgAIVRUVHc8/5PO66R1WAbOzX97q+ZtDEpR0AWTR7kVyWhCAAhgABCAAGEAAIIAQQQAggBBBACCCAEEEAIIIAQQAgggBBAACGAAEIAAYQAQgABhAACCAEEEAIIIAQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAsqeX5QWHKIcs/Ptl03lfL4zDFPWfBGmSpPn+IZzSH5KkCL5B+n+oklwz6Iz//R2QzFOabzhEmiRirAmZt/bl0w/dpMbLqeeo4wEdpC7zR5WAPKziHKtO7ql+ReKvIa9BxgNaL5ZtEkpeAGIVp5jKJa09xVo9vgSSzQcszdYvmOqjQNSQ6pHK6rO1n1Xj32788miwHLaZz1Tl9i/yayDlYJ/60/+lp8GSY7OY1B8E4p55bWmfquFk22GLuUUxi78cX+m+BjL2GLkhMrV+/muS6Sfic0CEp5T1Yu2OQdTzsKV0MJV73KVjroyTffxfuv5Tf3fd6iLT9wz8YdVHgUzF2Is9/Xhi5sYJqP1w/GUpjOiHVbaI0w2L+pg3GZzvtokcgHxWDXHaiy78l3sPke01qphamT5c+dqyeAGSumdL/mkggauTam0e3L/mPEiqtzKDbl0Z1Wn8xOa4ySo8X/7TQIJnY/seEKWf12UmC72CKP9xYjr19RPT7NNA+oMO+R0gwmlotAry+C6I0f59ch8yXVQOr0BKYcXt1IUYRyCt+Ur9HGsrQKI79WY9sY9ARPKlzFOFdb41ioD8b5Bp+mqeeRKAxINkESBFGpOpKhgv9OuYpH8A8l4Qa3qp60Kl2/k+rG2sWafuuyCBafb2j4JkgZUob3nWcmicpkxEgmTLLGejTxnWSWCi8lPmsk6DlIHFJv24ojiYyYoGacwL8zXTLEAVaDI/Ybb3NIgKDSv2oXpmHkvNs+PTpMASEdlk7fOZeRk37fwJ6yGnQarQsGIfqqcvx43rTOXY6jf7uKXdRzdLDRPbjIrx1cIj3Kr4KyBFezzgUGuR5893qkOQ19fR2uVBaU+r16LphJNOiatK7PeBZK/Kb+tUn71rcQjSvARpghfH/yG/D2RetTuI3N5QrMWdP46brP7FmKZ//CGQ9At9SL01DLkzY/Vs8Z97fQZ7gelw7jHqCz+/Wile5J4g3Vc79eb5a6oLSue+Ve83gaSv2jp5PxCzjzwFUm9zw9MllSMil1kS4d2E9SaQ1xNo9wMxx0+nQNLnew/WDHvveMAHYm08mofl3TFI/8pD3Q6kMAv6DIi2jTCwRJUvNdDYrrJum9oHhusCbWALonwxBRk1vXMnEGWuT5wAmfYuVGUYpJ7fUZujCN92hvzwWlrFgxSfANKb10DxIMbShnfrynyZZV30imA7P43ArXXHbvBVkTCIuGy25AdBrHmNeBCpL214QdLp9LZarG3IMWrmW0ehtuO7F2PS09UcgqS3B7FKPhpknrStD0HGF/vQRne37LwLG8EbHT4WxN7/Fg0yD9Yr/3br4nnstA+0Il6QxzdBmg8A6a2/IRbkcK9h/uzV8zywF/oSkOyageCPglRWgcWClHnEzs9q/t/SENVXgFijlsq3VtXdCsRp4qObrLLLgjuzSq3fX89ZZW6AfxNIzF6X9FYgThN/fk093KkvHX/hbWd+DqS/FUhlf+G3gohEXzVs3g9iDluWoaW8fL73QhB34u+tIHIf19nLuF4Q98a09Eynnl56q+ePgEhnX+dbQOp6H5XnJ0ACd8dFgkwf12nTOTcEqd2pom+CFF02TIPw6dKmrLS5qOtBpo8b5quUtrwrSGbuqPkeSJqllTFHO02NPxdMrm+y5LKdWyWXjw4vA5nGEtnjuyCFyHqNYvEolzmASm3zK1Eg5zr13lhqV1tlksnVw8Pkwgri7O07AVKLJkutRYw87bPlRpBpNXE8xGb+fhBlvEGrGPLqViu5sILIx9dAmqF1705sxF4M8+R8P5dOdQwi12fMnATpjJ2JSt/POIvU9wPJEs/jduJAjLvU0yFT0i64Yb1bsVi79dA4pEy3TzoHMq2O7Re4vXm5O9+l290NpE4CU+YRIMNye2iaqbVS2AUnn2fsekthYKReVNutVedA5juttyIXrT38mOds+ps9DWhwL7GWc61/DVKPzVN9UHDarf1icU98IOU8tm6L031Iq63t1tKzj3fe/FCpO4F0/i0Z2+yvA1KeGBjqj1qYx8/zoxpKZ1Yl367I1k+sfcft/QPy9csXy/32qX1qLZsrryG5BGQaRj0vc/b7N54XXq293TCLB5HO42Fy517obW19b+qjl3CHp0fdLJcWvmdy1etESi/uAdJrs1hTaUklHuW8qSDdC3UfXVR5cnD3rAFSSqtFb7z7eapErx7rC739jCXfbK3aWiipjXo8UbmxXPa7QQq9R289j2Gr88N7Ag5AlHPRKc37pNZv0CZtX1tVMG6rm8qW1/KlCgQvcMss933ybwXZz3dReW5yce4ByZtHFIhwT9kmjxg8BzbKDUe1PB9edBJqSN7/KM1LmqyuMZ5BpeTUw1aD/uDI0relPfSHa/Wn8Pxq1BNfxy/h3IdwOJqIKumb9CHvTqMefyY82RoQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAGEAAIIAQQQAgggBBBACCAEEEAIIIAQQAAhgABCACGAAEIAAYQAAggBBBACCAEEEAIIIAQQQAggfyL/BBgA8PgLdH0TBtkAAAAASUVORK5CYII='
|
||||
},
|
||||
// 图片进入可见区域前多少像素时,单位rpx,开始加载图片
|
||||
// 负数为图片超出屏幕底部多少距离后触发懒加载,正数为图片顶部距离屏幕底部多少距离时触发(图片还没出现在屏幕上)
|
||||
threshold: {
|
||||
type: [Number, String],
|
||||
default: 100
|
||||
},
|
||||
// 淡入淡出动画的过渡时间
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 500
|
||||
},
|
||||
// 渡效果的速度曲线,各个之间差别不大,因为这是淡入淡出,且时间很短,不是那些变形或者移动的情况,会明显
|
||||
// linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n);
|
||||
effect: {
|
||||
type: String,
|
||||
default: 'ease-in-out'
|
||||
},
|
||||
// 是否使用过渡效果
|
||||
isEffect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 圆角值
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 图片高度,单位rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: '450'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
opacity: 1,
|
||||
time: this.duration,
|
||||
loadStatus: '', // 默认是懒加载中的状态
|
||||
isError: false, // 图片加载失败
|
||||
elIndex: this.$u.guid()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 将threshold从rpx转为px
|
||||
getThreshold() {
|
||||
// 先取绝对值,因为threshold可能是负数,最后根据this.threshold是正数或者负数,重新还原
|
||||
let thresholdPx = uni.upx2px(Math.abs(this.threshold));
|
||||
return this.threshold < 0 ? -thresholdPx : thresholdPx;
|
||||
},
|
||||
// 计算图片的高度,可能为auto,带%,或者直接数值
|
||||
imgHeight() {
|
||||
return this.$u.addUnit(this.height);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 由于一些特殊原因,不能将此变量放到data中定义
|
||||
this.observer = {};
|
||||
},
|
||||
watch: {
|
||||
isShow(nVal) {
|
||||
// 如果是不开启过渡效果,直接返回
|
||||
if (!this.isEffect) return;
|
||||
this.time = 0;
|
||||
// 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的白色),再改成1,是为了获得过渡效果
|
||||
this.opacity = 0;
|
||||
// 延时30ms,否则在浏览器H5,过渡效果无效
|
||||
setTimeout(() => {
|
||||
this.time = this.duration;
|
||||
this.opacity = 1;
|
||||
}, 30)
|
||||
},
|
||||
// 图片路径发生变化时,需要重新标记一些变量,否则会一直卡在某一个状态,比如isError
|
||||
image(n) {
|
||||
if(!n) {
|
||||
// 如果传入null或者'',或者undefined,标记为错误状态
|
||||
this.isError = true;
|
||||
} else {
|
||||
this.init();
|
||||
this.isError = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 用于重新初始化
|
||||
init() {
|
||||
this.isError = false;
|
||||
this.loadStatus = '';
|
||||
},
|
||||
// 点击图片触发的事件,loadlazy-还是懒加载中状态,loading-图片正在加载,loaded-图片加加载完成
|
||||
clickImg() {
|
||||
let whichImg = '';
|
||||
// 如果isShow为false,意味着图片还没开始加载,点击的只能是最开始的占位图
|
||||
if (this.isShow == false) whichImg = 'lazyImg';
|
||||
// 如果isError为true,意味着图片加载失败,这是只剩下错误的占位图,所以点击的只能是错误占位图
|
||||
// 当然,也可以给错误的占位图元素绑定点击事件,看你喜欢~
|
||||
else if (this.isError == true) whichImg = 'errorImg';
|
||||
// 总共三张图片,除了两个占位图,剩下的只能是正常的那张图片了
|
||||
else whichImg = 'realImg';
|
||||
// 只通知当前图片的index
|
||||
this.$emit('click', this.index);
|
||||
},
|
||||
// 图片加载完成事件,可能是加载占位图时触发,也可能是加载真正的图片完成时触发,通过isShow区分
|
||||
imgLoaded() {
|
||||
// 占位图加载完成
|
||||
if (this.loadStatus == '') {
|
||||
this.loadStatus = 'lazyed';
|
||||
}
|
||||
// 真正的图片加载完成
|
||||
else if (this.loadStatus == 'lazyed') {
|
||||
this.loadStatus = 'loaded';
|
||||
this.$emit('load', this.index);
|
||||
}
|
||||
},
|
||||
// 错误的图片加载完成
|
||||
errorImgLoaded() {
|
||||
this.$emit('error', this.index);
|
||||
},
|
||||
// 图片加载失败
|
||||
loadError() {
|
||||
this.isError = true;
|
||||
},
|
||||
disconnectObserver(observerName) {
|
||||
const observer = this[observerName];
|
||||
observer && observer.disconnect();
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 销毁页面时,可能还没触发某张很底部的懒加载图片,所以把这个事件给去掉
|
||||
//observer.disconnect();
|
||||
},
|
||||
mounted() {
|
||||
// 此uOnReachBottom事件由mixin.js发出,目的是让页面到底时,保证所有图片都进行加载,做到绝对稳定且可靠
|
||||
this.$nextTick(() => {
|
||||
uni.$once('uOnReachBottom', () => {
|
||||
if (!this.isShow) this.isShow = true;
|
||||
});
|
||||
})
|
||||
// mounted的时候,不一定挂载了这个元素,延时30ms,否则会报错或者不报错,但是也没有效果
|
||||
setTimeout(() => {
|
||||
// 这里是组件内获取布局状态,不能用uni.createIntersectionObserver,而必须用this.createIntersectionObserver
|
||||
this.disconnectObserver('contentObserver');
|
||||
const contentObserver = uni.createIntersectionObserver(this);
|
||||
// 要理解这里怎么计算的,请看这个:
|
||||
// https://blog.csdn.net/qq_25324335/article/details/83687695
|
||||
contentObserver.relativeToViewport({
|
||||
bottom: this.getThreshold,
|
||||
}).observe('.u-lazy-item-' + this.elIndex, (res) => {
|
||||
if (res.intersectionRatio > 0) {
|
||||
// 懒加载状态改变
|
||||
this.isShow = true;
|
||||
// 如果图片已经加载,去掉监听,减少性能的消耗
|
||||
this.disconnectObserver('contentObserver');
|
||||
}
|
||||
})
|
||||
this.contentObserver = contentObserver;
|
||||
}, 30)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-wrap {
|
||||
background-color: #eee;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-lazy-item {
|
||||
width: 100%;
|
||||
// 骗系统开启硬件加速
|
||||
transform: transition3d(0, 0, 0);
|
||||
// 防止图片加载“闪一下”
|
||||
will-change: transform;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,147 @@
|
|||
<template>
|
||||
<view class="u-progress" :style="{
|
||||
borderRadius: round ? '100rpx' : 0,
|
||||
height: height + 'rpx',
|
||||
backgroundColor: inactiveColor
|
||||
}">
|
||||
<view :class="[
|
||||
type ? `u-type-${type}-bg` : '',
|
||||
striped ? 'u-striped' : '',
|
||||
striped && stripedActive ? 'u-striped-active' : ''
|
||||
]" class="u-active" :style="[progressStyle]">
|
||||
<slot v-if="$slots.default || $slots.$default" />
|
||||
<block v-else-if="showPercent">
|
||||
{{percent + '%'}}
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* lineProgress 线型进度条
|
||||
* @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。
|
||||
* @tutorial https://www.uviewui.com/components/lineProgress.html
|
||||
* @property {String Number} percent 进度条百分比值,为数值类型,0-100
|
||||
* @property {Boolean} round 进度条两端是否为半圆(默认true)
|
||||
* @property {String} type 如设置,active-color值将会失效
|
||||
* @property {String} active-color 进度条激活部分的颜色(默认#19be6b)
|
||||
* @property {String} inactive-color 进度条的底色(默认#ececec)
|
||||
* @property {Boolean} show-percent 是否在进度条内部显示当前的百分比值数值(默认true)
|
||||
* @property {String Number} height 进度条的高度,单位rpx(默认28)
|
||||
* @property {Boolean} striped 是否显示进度条激活部分的条纹(默认false)
|
||||
* @property {Boolean} striped-active 条纹是否具有动态效果(默认false)
|
||||
* @example <u-line-progress :percent="70" :show-percent="true"></u-line-progress>
|
||||
*/
|
||||
export default {
|
||||
name: "u-line-progress",
|
||||
props: {
|
||||
// 两端是否显示半圆形
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 主题颜色
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 激活部分的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#19be6b'
|
||||
},
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#ececec'
|
||||
},
|
||||
// 进度百分比,数值
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否在进度条内部显示百分比的值
|
||||
showPercent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 进度条的高度,单位rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 28
|
||||
},
|
||||
// 是否显示条纹
|
||||
striped: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 条纹是否显示活动状态
|
||||
stripedActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
progressStyle() {
|
||||
let style = {};
|
||||
style.width = this.percent + '%';
|
||||
if(this.activeColor) style.backgroundColor = this.activeColor;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-progress {
|
||||
overflow: hidden;
|
||||
height: 15px;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-radius: 100rpx;
|
||||
}
|
||||
|
||||
.u-active {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
@include vue-flex;
|
||||
justify-items: flex-end;
|
||||
justify-content: space-around;
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.u-striped {
|
||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||
background-size: 39px 39px;
|
||||
}
|
||||
|
||||
.u-striped-active {
|
||||
animation: progress-stripes 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes progress-stripes {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 39px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<view class="u-line" :style="[lineStyle]">
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* line 线条
|
||||
* @description 此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单
|
||||
* @tutorial https://www.uviewui.com/components/line.html
|
||||
* @property {String} color 线条的颜色(默认#e4e7ed)
|
||||
* @property {String} length 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带rpx单位的值等
|
||||
* @property {String} direction 线条的方向,row-横向,col-竖向(默认row)
|
||||
* @property {String} border-style 线条的类型,solid-实线,dashed-方形虚线,dotted-圆点虚线(默认solid)
|
||||
* @property {Boolean} hair-line 是否显示细线条(默认true)
|
||||
* @property {String} margin 线条与上下左右元素的间距,字符串形式,如"30rpx"
|
||||
* @example <u-line color="red"></u-line>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-line',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#e4e7ed'
|
||||
},
|
||||
// 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带rpx单位的值等
|
||||
length: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
// 线条方向,col-竖向,row-横向
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
// 是否显示细边框
|
||||
hairLine: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 线条与上下左右元素的间距,字符串形式,如"30rpx"、"20rpx 30rpx"
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
// 线条的类型,solid-实线,dashed-方形虚线,dotted-圆点虚线
|
||||
borderStyle: {
|
||||
type: String,
|
||||
default: 'solid'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lineStyle() {
|
||||
let style = {};
|
||||
style.margin = this.margin;
|
||||
// 如果是水平线条,边框高度为1px,再通过transform缩小一半,就是0.5px了
|
||||
if(this.direction == 'row') {
|
||||
// 此处采用兼容分开写,兼容nvue的写法
|
||||
style.borderBottomWidth = '1px';
|
||||
style.borderBottomStyle = this.borderStyle;
|
||||
style.width = this.$u.addUnit(this.length);
|
||||
if(this.hairLine) style.transform = 'scaleY(0.5)';
|
||||
} else {
|
||||
// 如果是竖向线条,边框宽度为1px,再通过transform缩小一半,就是0.5px了
|
||||
style.borderLeftWidth = '1px';
|
||||
style.borderLeftStyle = this.borderStyle;
|
||||
style.height = this.$u.addUnit(this.length);
|
||||
if(this.hairLine) style.transform = 'scaleX(0.5)';
|
||||
}
|
||||
style.borderColor = this.color;
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-line {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,89 @@
|
|||
<template>
|
||||
<text class="u-link" @tap.stop="openLink" :style="{
|
||||
color: color,
|
||||
fontSize: fontSize + 'rpx',
|
||||
borderBottom: underLine ? `1px solid ${lineColor ? lineColor : color}` : 'none',
|
||||
paddingBottom: underLine ? '0rpx' : '0'
|
||||
}">
|
||||
<slot></slot>
|
||||
</text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* link 超链接
|
||||
* @description 该组件为超链接组件,在不同平台有不同表现形式:在APP平台会通过plus环境打开内置浏览器,在小程序中把链接复制到粘贴板,同时提示信息,在H5中通过window.open打开链接。
|
||||
* @tutorial https://www.uviewui.com/components/link.html
|
||||
* @property {String} color 文字颜色(默认#606266)
|
||||
* @property {String Number} font-size 字体大小,单位rpx(默认28)
|
||||
* @property {Boolean} under-line 是否显示下划线(默认false)
|
||||
* @property {String} href 跳转的链接,要带上http(s)
|
||||
* @property {String} line-color 下划线颜色,默认同color参数颜色
|
||||
* @property {String} mp-tips 各个小程序平台把链接复制到粘贴板后的提示语(默认“链接已复制,请在浏览器打开”)
|
||||
* @example <u-link href="http://www.uviewui.com">蜀道难,难于上青天</u-link>
|
||||
*/
|
||||
export default {
|
||||
name: "u-link",
|
||||
props: {
|
||||
// 文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 字体大小,单位rpx
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: 28
|
||||
},
|
||||
// 是否显示下划线
|
||||
underLine: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 要跳转的链接
|
||||
href: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 小程序中复制到粘贴板的提示语
|
||||
mpTips: {
|
||||
type: String,
|
||||
default: '链接已复制,请在浏览器打开'
|
||||
},
|
||||
// 下划线颜色
|
||||
lineColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openLink() {
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openURL(this.href)
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
window.open(this.href)
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
uni.setClipboardData({
|
||||
data: this.href,
|
||||
success: () => {
|
||||
uni.hideToast();
|
||||
this.$nextTick(() => {
|
||||
this.$u.toast(this.mpTips);
|
||||
})
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-link {
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<view class="u-loading-page">
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<view v-if="show" class="u-loading" :class="mode == 'circle' ? 'u-loading-circle' : 'u-loading-flower'" :style="[cricleStyle]">
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* loading 加载动画
|
||||
* @description 警此组件为一个小动画,目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。
|
||||
* @tutorial https://www.uviewui.com/components/loading.html
|
||||
* @property {String} mode 模式选择,见官网说明(默认circle)
|
||||
* @property {String} color 动画活动区域的颜色,只对 mode = flower 模式有效(默认#c7c7c7)
|
||||
* @property {String Number} size 加载图标的大小,单位rpx(默认34)
|
||||
* @property {Boolean} show 是否显示动画(默认true)
|
||||
* @example <u-loading mode="circle"></u-loading>
|
||||
*/
|
||||
export default {
|
||||
name: "u-loading",
|
||||
props: {
|
||||
// 动画的类型
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 动画的颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#c7c7c7'
|
||||
},
|
||||
// 加载图标的大小,单位rpx
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: '34'
|
||||
},
|
||||
// 是否显示动画
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 加载中圆圈动画的样式
|
||||
cricleStyle() {
|
||||
let style = {};
|
||||
style.width = this.size + 'rpx';
|
||||
style.height = this.size + 'rpx';
|
||||
if (this.mode == 'circle') style.borderColor = `#e4e4e4 #e4e4e4 #e4e4e4 ${this.color ? this.color : '#c7c7c7'}`;
|
||||
return style;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-loading-circle {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
vertical-align: middle;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
background: 0 0;
|
||||
border-radius: 50%;
|
||||
border: 2px solid;
|
||||
border-color: #e5e5e5 #e5e5e5 #e5e5e5 #8f8d8e;
|
||||
animation: u-circle 1s linear infinite;
|
||||
}
|
||||
|
||||
.u-loading-flower {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
-webkit-animation: a 1s steps(12) infinite;
|
||||
animation: u-flower 1s steps(12) infinite;
|
||||
background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
@keyframes u-flower {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: rotate(1turn);
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes u-circle {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,203 @@
|
|||
<template>
|
||||
<view class="u-load-more-wrap" :style="{
|
||||
backgroundColor: bgColor,
|
||||
marginBottom: marginBottom + 'rpx',
|
||||
marginTop: marginTop + 'rpx',
|
||||
height: $u.addUnit(height)
|
||||
}">
|
||||
<u-line color="#d4d4d4" length="50"></u-line>
|
||||
<!-- 加载中和没有更多的状态才显示两边的横线 -->
|
||||
<view :class="status == 'loadmore' || status == 'nomore' ? 'u-more' : ''" class="u-load-more-inner">
|
||||
<view class="u-loadmore-icon-wrap">
|
||||
<u-loading class="u-loadmore-icon" :color="iconColor" :mode="iconType == 'circle' ? 'circle' : 'flower'" :show="status == 'loading' && icon"></u-loading>
|
||||
</view>
|
||||
<!-- 如果没有更多的状态下,显示内容为dot(粗点),加载特定样式 -->
|
||||
<view class="u-line-1" :style="[loadTextStyle]" :class="[(status == 'nomore' && isDot == true) ? 'u-dot-text' : 'u-more-text']" @tap="loadMore">
|
||||
{{ showText }}
|
||||
</view>
|
||||
</view>
|
||||
<u-line color="#d4d4d4" length="50"></u-line>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* loadmore 加载更多
|
||||
* @description 此组件一般用于标识页面底部加载数据时的状态。
|
||||
* @tutorial https://www.uviewui.com/components/loadMore.html
|
||||
* @property {String} status 组件状态(默认loadmore)
|
||||
* @property {String} bg-color 组件背景颜色,在页面是非白色时会用到(默认#ffffff)
|
||||
* @property {Boolean} icon 加载中时是否显示图标(默认true)
|
||||
* @property {String} icon-type 加载中时的图标类型(默认circle)
|
||||
* @property {String} icon-color icon-type为circle时有效,加载中的动画图标的颜色(默认#b7b7b7)
|
||||
* @property {Boolean} is-dot status为nomore时,内容显示为一个"●"(默认false)
|
||||
* @property {String} color 字体颜色(默认#606266)
|
||||
* @property {String Number} margin-top 到上一个相邻元素的距离
|
||||
* @property {String Number} margin-bottom 到下一个相邻元素的距离
|
||||
* @property {Object} load-text 自定义显示的文字,见上方说明示例
|
||||
* @event {Function} loadmore status为loadmore时,点击组件会发出此事件
|
||||
* @example <u-loadmore :status="status" icon-type="iconType" load-text="loadText" />
|
||||
*/
|
||||
export default {
|
||||
name: "u-loadmore",
|
||||
props: {
|
||||
// 组件背景色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
// 是否显示加载中的图标
|
||||
icon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: String,
|
||||
default: '28'
|
||||
},
|
||||
// 字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 组件状态,loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
|
||||
status: {
|
||||
type: String,
|
||||
default: 'loadmore'
|
||||
},
|
||||
// 加载中状态的图标,flower-花朵状图标,circle-圆圈状图标
|
||||
iconType: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 显示的文字
|
||||
loadText: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
loadmore: '加载更多',
|
||||
loading: '正在加载...',
|
||||
nomore: '没有更多了'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 在“没有更多”状态下,是否显示粗点
|
||||
isDot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 加载中显示圆圈动画时,动画的颜色
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#b7b7b7'
|
||||
},
|
||||
// 上边距
|
||||
marginTop: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 下边距
|
||||
marginBottom: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 高度,单位rpx
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 粗点
|
||||
dotText: "●"
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 加载的文字显示的样式
|
||||
loadTextStyle() {
|
||||
return {
|
||||
color: this.color,
|
||||
fontSize: this.fontSize + 'rpx',
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
backgroundColor: this.bgColor,
|
||||
// 如果是加载中状态,动画和文字需要距离近一点
|
||||
}
|
||||
},
|
||||
// 加载中圆圈动画的样式
|
||||
cricleStyle() {
|
||||
return {
|
||||
borderColor: `#e5e5e5 #e5e5e5 #e5e5e5 ${this.circleColor}`
|
||||
}
|
||||
},
|
||||
// 加载中花朵动画形式
|
||||
// 动画由base64图片生成,暂不支持修改
|
||||
flowerStyle() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
// 显示的提示文字
|
||||
showText() {
|
||||
let text = '';
|
||||
if(this.status == 'loadmore') text = this.loadText.loadmore;
|
||||
else if(this.status == 'loading') text = this.loadText.loading;
|
||||
else if(this.status == 'nomore' && this.isDot) text = this.dotText;
|
||||
else text = this.loadText.nomore;
|
||||
return text;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadMore() {
|
||||
// 只有在“加载更多”的状态下才发送点击事件,内容不满一屏时无法触发底部上拉事件,所以需要点击来触发
|
||||
if(this.status == 'loadmore') this.$emit('loadmore');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
/* #ifdef MP */
|
||||
// 在mp.scss中,赋予了u-line为flex: 1,这里需要一个明确的长度,所以重置掉它
|
||||
// 在组件内部,把组件名(u-line)当做选择器,在微信开发工具会提示不合法,但不影响使用
|
||||
u-line {
|
||||
flex: none;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.u-load-more-wrap {
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-load-more-inner {
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 12rpx;
|
||||
}
|
||||
|
||||
.u-more {
|
||||
position: relative;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-dot-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.u-loadmore-icon-wrap {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.u-loadmore-icon {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<view class="u-mask" hover-stop-propagation :style="[maskStyle, zoomStyle]" @tap="click" @touchmove.stop.prevent="() => {}" :class="{
|
||||
'u-mask-zoom': zoom,
|
||||
'u-mask-show': show
|
||||
}">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* mask 遮罩
|
||||
* @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景
|
||||
* @tutorial https://www.uviewui.com/components/mask.html
|
||||
* @property {Boolean} show 是否显示遮罩(默认false)
|
||||
* @property {String Number} z-index z-index 层级(默认1070)
|
||||
* @property {Object} custom-style 自定义样式对象,见上方说明
|
||||
* @property {String Number} duration 动画时长,单位毫秒(默认300)
|
||||
* @property {Boolean} zoom 是否使用scale对遮罩进行缩放(默认true)
|
||||
* @property {Boolean} mask-click-able 遮罩是否可点击,为false时点击不会发送click事件(默认true)
|
||||
* @event {Function} click mask-click-able为true时,点击遮罩发送此事件
|
||||
* @example <u-mask :show="show" @click="show = false"></u-mask>
|
||||
*/
|
||||
export default {
|
||||
name: "u-mask",
|
||||
props: {
|
||||
// 是否显示遮罩
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 层级z-index
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 用户自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 遮罩的动画样式, 是否使用使用zoom进行scale进行缩放
|
||||
zoom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 遮罩的过渡时间,单位为ms
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 300
|
||||
},
|
||||
// 是否可以通过点击遮罩进行关闭
|
||||
maskClickAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
zoomStyle: {
|
||||
transform: ''
|
||||
},
|
||||
scale: 'scale(1.2, 1.2)'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(n) {
|
||||
if(n && this.zoom) {
|
||||
// 当展示遮罩的时候,设置scale为1,达到缩小(原来为1.2)的效果
|
||||
this.zoomStyle.transform = 'scale(1, 1)';
|
||||
} else if(!n && this.zoom) {
|
||||
// 当隐藏遮罩的时候,设置scale为1.2,达到放大(因为显示遮罩时已重置为1)的效果
|
||||
this.zoomStyle.transform = this.scale;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
maskStyle() {
|
||||
let style = {};
|
||||
style.backgroundColor = "rgba(0, 0, 0, 0.6)";
|
||||
if(this.show) style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.mask;
|
||||
else style.zIndex = -1;
|
||||
style.transition = `all ${this.duration / 1000}s ease-in-out`;
|
||||
// 判断用户传递的对象是否为空,不为空就进行合并
|
||||
if (Object.keys(this.customStyle).length) style = {
|
||||
...style,
|
||||
...this.customStyle
|
||||
};
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (!this.maskClickAble) return;
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.u-mask-show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.u-mask-zoom {
|
||||
transform: scale(1.2, 1.2);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,311 @@
|
|||
<template>
|
||||
<view class="u-char-box">
|
||||
<view class="u-char-flex">
|
||||
<input :disabled="disabledKeyboard" :value="valueModel" type="number" :focus="focus" :maxlength="maxlength" class="u-input" @input="getVal"/>
|
||||
<view v-for="(item, index) in loopCharArr" :key="index">
|
||||
<view :class="[breathe && charArrLength == index ? 'u-breathe' : '', 'u-char-item',
|
||||
charArrLength === index && mode == 'box' ? 'u-box-active' : '',
|
||||
mode === 'box' ? 'u-box' : '']" :style="{
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
fontSize: fontSize + 'rpx',
|
||||
width: width + 'rpx',
|
||||
height: width + 'rpx',
|
||||
color: inactiveColor,
|
||||
borderColor: charArrLength === index && mode == 'box' ? activeColor : inactiveColor
|
||||
}">
|
||||
<view class="u-placeholder-line" :style="{
|
||||
display: charArrLength === index ? 'block' : 'none',
|
||||
height: width * 0.5 +'rpx'
|
||||
}"
|
||||
v-if="mode !== 'middleLine'"
|
||||
></view>
|
||||
<view v-if="mode === 'middleLine' && charArrLength <= index" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-middle-line-active' : '']"
|
||||
class="u-middle-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view>
|
||||
<view v-if="mode === 'bottomLine'" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-buttom-line-active' : '']"
|
||||
class="u-bottom-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view>
|
||||
<block v-if="!dotFill"> {{ charArr[index] ? charArr[index] : ''}}</block>
|
||||
<block v-else>
|
||||
<text class="u-dot">{{ charArr[index] ? '●' : ''}}</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* messageInput 验证码输入框
|
||||
* @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用
|
||||
* @tutorial https://www.uviewui.com/components/messageInput.html
|
||||
* @property {String Number} maxlength 输入字符个数(默认4)
|
||||
* @property {Boolean} dot-fill 是否用圆点填充(默认false)
|
||||
* @property {String} mode 模式选择,见上方"基本使用"说明(默认box)
|
||||
* @property {String Number} value 预置值
|
||||
* @property {Boolean} breathe 是否开启呼吸效果,见上方说明(默认true)
|
||||
* @property {Boolean} focus 是否自动获取焦点(默认false)
|
||||
* @property {Boolean} bold 字体和输入横线是否加粗(默认true)
|
||||
* @property {String Number} font-size 字体大小,单位rpx(默认60)
|
||||
* @property {String} active-color 当前激活输入框的样式(默认#2979ff)
|
||||
* @property {String} inactive-color 非激活输入框的样式,文字颜色同此值(默认#606266)
|
||||
* @property {String | Number} width 输入框宽度,单位rpx,高等于宽(默认80)
|
||||
* @property {Boolean} disabled-keyboard 禁止点击输入框唤起系统键盘(默认false)
|
||||
* @event {Function} change 输入内容发生改变时触发,具体见官网说明
|
||||
* @event {Function} finish 输入字符个数达maxlength值时触发,见官网说明
|
||||
* @example <u-message-input mode="bottomLine"></u-message-input>
|
||||
*/
|
||||
export default {
|
||||
name: "u-message-input",
|
||||
props: {
|
||||
// 最大输入长度
|
||||
maxlength: {
|
||||
type: [Number, String],
|
||||
default: 4
|
||||
},
|
||||
// 是否用圆点填充
|
||||
dotFill: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示模式,box-盒子模式,bottomLine-横线在底部模式,middleLine-横线在中部模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: "box"
|
||||
},
|
||||
// 预置值
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 当前激活输入item,是否带有呼吸效果
|
||||
breathe: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否自动获取焦点
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 字体是否加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: 60
|
||||
},
|
||||
// 激活样式
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 未激活的样式
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 输入框的大小,单位rpx,宽等于高
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: '80'
|
||||
},
|
||||
// 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true
|
||||
disabledKeyboard: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// maxlength: {
|
||||
// // 此值设置为true,会在组件加载后无需maxlength变化就会执行一次本监听函数,无需再created生命周期中处理
|
||||
// immediate: true,
|
||||
// handler(val) {
|
||||
// this.maxlength = Number(val);
|
||||
// }
|
||||
// },
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
// 转为字符串
|
||||
val = String(val);
|
||||
// 超出部分截掉
|
||||
this.valueModel = val.substring(0, this.maxlength);
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueModel: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否显示呼吸灯效果
|
||||
animationClass() {
|
||||
return (index) => {
|
||||
if (this.breathe && this.charArr.length == index) return 'u-breathe';
|
||||
else return '';
|
||||
}
|
||||
},
|
||||
// 用于显示字符
|
||||
charArr() {
|
||||
return this.valueModel.split('');
|
||||
},
|
||||
charArrLength() {
|
||||
return this.charArr.length;
|
||||
},
|
||||
// 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
|
||||
loopCharArr() {
|
||||
return new Array(this.maxlength);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getVal(e) {
|
||||
let {
|
||||
value
|
||||
} = e.detail
|
||||
this.valueModel = value;
|
||||
// 判断长度是否超出了maxlength值,理论上不会发生,因为input组件设置了maxlength属性值
|
||||
if (String(value).length > this.maxlength) return;
|
||||
// 未达到maxlength之前,发送change事件,达到后发送finish事件
|
||||
this.$emit('change', value);
|
||||
if (String(value).length == this.maxlength) {
|
||||
this.$emit('finish', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
@keyframes breathe {
|
||||
0% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.u-char-box {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.u-char-flex {
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.u-input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 200%;
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
z-index: 9;
|
||||
opacity: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.u-char-item {
|
||||
position: relative;
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
margin: 10rpx 10rpx;
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
color: $u-main-color;
|
||||
line-height: 90rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-middle-line {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.u-box {
|
||||
box-sizing: border-box;
|
||||
border: 2rpx solid #cccccc;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.u-box-active {
|
||||
overflow: hidden;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-duration: 1500ms;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
border: 2rpx solid $u-type-primary;
|
||||
}
|
||||
|
||||
.u-middle-line-active {
|
||||
background: $u-type-primary;
|
||||
}
|
||||
|
||||
.u-breathe {
|
||||
animation: breathe 2s infinite ease;
|
||||
}
|
||||
|
||||
.u-placeholder-line {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: none;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 2rpx;
|
||||
height: 40rpx;
|
||||
background: #333333;
|
||||
animation: twinkling 1.5s infinite ease;
|
||||
}
|
||||
|
||||
.u-animation-breathe {
|
||||
animation-name: breathe;
|
||||
}
|
||||
|
||||
.u-dot {
|
||||
font-size: 34rpx;
|
||||
line-height: 34rpx;
|
||||
}
|
||||
|
||||
.u-middle-line {
|
||||
height: 4px;
|
||||
background: #000000;
|
||||
width: 80%;
|
||||
position: absolute;
|
||||
border-radius: 2px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.u-buttom-line-active {
|
||||
background: $u-type-primary;
|
||||
}
|
||||
|
||||
.u-bottom-line {
|
||||
height: 4px;
|
||||
background: #000000;
|
||||
width: 80%;
|
||||
position: absolute;
|
||||
border-radius: 2px;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,283 @@
|
|||
<template>
|
||||
<view>
|
||||
<u-popup :zoom="zoom" mode="center" :popup="false" :z-index="uZIndex" v-model="value" :length="width"
|
||||
:mask-close-able="maskCloseAble" :border-radius="borderRadius" @close="popupClose" :negative-top="negativeTop">
|
||||
<view class="u-model">
|
||||
<view v-if="showTitle" class="u-model__title u-line-1" :style="[titleStyle]">{{ title }}</view>
|
||||
<view class="u-model__content">
|
||||
<view :style="[contentStyle]" v-if="$slots.default || $slots.$default">
|
||||
<slot />
|
||||
</view>
|
||||
<view v-else class="u-model__content__message" :style="[contentStyle]">{{ content }}</view>
|
||||
</view>
|
||||
<view class="u-model__footer u-border-top" v-if="showCancelButton || showConfirmButton">
|
||||
<view v-if="showCancelButton" :hover-stay-time="100" hover-class="u-model__btn--hover" class="u-model__footer__button"
|
||||
:style="[cancelBtnStyle]" @tap="cancel">
|
||||
{{cancelText}}
|
||||
</view>
|
||||
<view v-if="showConfirmButton || $slots['confirm-button']" :hover-stay-time="100" :hover-class="asyncClose ? 'none' : 'u-model__btn--hover'"
|
||||
class="u-model__footer__button hairline-left" :style="[confirmBtnStyle]" @tap="confirm">
|
||||
<slot v-if="$slots['confirm-button']" name="confirm-button"></slot>
|
||||
<block v-else>
|
||||
<u-loading mode="circle" :color="confirmColor" v-if="loading"></u-loading>
|
||||
<block v-else>
|
||||
{{confirmText}}
|
||||
</block>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* modal 模态框
|
||||
* @description 弹出模态框,常用于消息提示、消息确认、在当前页面内完成特定的交互操作
|
||||
* @tutorial https://www.uviewui.com/components/modal.html
|
||||
* @property {Boolean} value 是否显示模态框
|
||||
* @property {String | Number} z-index 层级
|
||||
* @property {String} title 模态框标题(默认"提示")
|
||||
* @property {String | Number} width 模态框宽度(默认600)
|
||||
* @property {String} content 模态框内容(默认"内容")
|
||||
* @property {Boolean} show-title 是否显示标题(默认true)
|
||||
* @property {Boolean} async-close 是否异步关闭,只对确定按钮有效(默认false)
|
||||
* @property {Boolean} show-confirm-button 是否显示确认按钮(默认true)
|
||||
* @property {Stringr | Number} negative-top modal往上偏移的值
|
||||
* @property {Boolean} show-cancel-button 是否显示取消按钮(默认false)
|
||||
* @property {Boolean} mask-close-able 是否允许点击遮罩关闭modal(默认false)
|
||||
* @property {String} confirm-text 确认按钮的文字内容(默认"确认")
|
||||
* @property {String} cancel-text 取消按钮的文字内容(默认"取消")
|
||||
* @property {String} cancel-color 取消按钮的颜色(默认"#606266")
|
||||
* @property {String} confirm-color 确认按钮的文字内容(默认"#2979ff")
|
||||
* @property {String | Number} border-radius 模态框圆角值,单位rpx(默认16)
|
||||
* @property {Object} title-style 自定义标题样式,对象形式
|
||||
* @property {Object} content-style 自定义内容样式,对象形式
|
||||
* @property {Object} cancel-style 自定义取消按钮样式,对象形式
|
||||
* @property {Object} confirm-style 自定义确认按钮样式,对象形式
|
||||
* @property {Boolean} zoom 是否开启缩放模式(默认true)
|
||||
* @event {Function} confirm 确认按钮被点击
|
||||
* @event {Function} cancel 取消按钮被点击
|
||||
* @example <u-modal :src="title" :content="content"></u-modal>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-modal',
|
||||
props: {
|
||||
// 是否显示Modal
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 层级z-index
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: [String],
|
||||
default: '提示'
|
||||
},
|
||||
// 弹窗宽度,可以是数值(rpx),百分比,auto等
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 600
|
||||
},
|
||||
// 弹窗内容
|
||||
content: {
|
||||
type: String,
|
||||
default: '内容'
|
||||
},
|
||||
// 是否显示标题
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示确认按钮
|
||||
showConfirmButton: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示取消按钮
|
||||
showCancelButton: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 确认文案
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认'
|
||||
},
|
||||
// 取消文案
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 确认按钮颜色
|
||||
confirmColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 取消文字颜色
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 圆角值
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: 16
|
||||
},
|
||||
// 标题的样式
|
||||
titleStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 内容的样式
|
||||
contentStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 取消按钮的样式
|
||||
cancelStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 确定按钮的样式
|
||||
confirmStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否开启缩放效果
|
||||
zoom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否异步关闭,只对确定按钮有效
|
||||
asyncClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许点击遮罩关闭modal
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 给一个负的margin-top,往上偏移,避免和键盘重合的情况
|
||||
negativeTop: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false, // 确认按钮是否正在加载中
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cancelBtnStyle() {
|
||||
return Object.assign({
|
||||
color: this.cancelColor
|
||||
}, this.cancelStyle);
|
||||
},
|
||||
confirmBtnStyle() {
|
||||
return Object.assign({
|
||||
color: this.confirmColor
|
||||
}, this.confirmStyle);
|
||||
},
|
||||
uZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 如果是异步关闭时,外部修改v-model的值为false时,重置内部的loading状态
|
||||
// 避免下次打开的时候,状态混乱
|
||||
value(n) {
|
||||
if (n === true) this.loading = false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
confirm() {
|
||||
// 异步关闭
|
||||
if (this.asyncClose) {
|
||||
this.loading = true;
|
||||
} else {
|
||||
this.$emit('input', false);
|
||||
}
|
||||
this.$emit('confirm');
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('cancel');
|
||||
this.$emit('input', false);
|
||||
// 目前popup弹窗关闭有一个延时操作,此处做一个延时
|
||||
// 避免确认按钮文字变成了"确定"字样,modal还没消失,造成视觉不好的效果
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, 300);
|
||||
},
|
||||
// 点击遮罩关闭modal,设置v-model的值为false,否则无法第二次弹起modal
|
||||
popupClose() {
|
||||
this.$emit('input', false);
|
||||
},
|
||||
// 清除加载中的状态
|
||||
clearLoading() {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-model {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
font-size: 32rpx;
|
||||
background-color: #fff;
|
||||
|
||||
&__btn--hover {
|
||||
background-color: rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
&__title {
|
||||
padding-top: 48rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
&__content {
|
||||
&__message {
|
||||
padding: 48rpx;
|
||||
font-size: 30rpx;
|
||||
text-align: center;
|
||||
color: $u-content-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
@include vue-flex;
|
||||
|
||||
&__button {
|
||||
flex: 1;
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
font-size: 32rpx;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,315 @@
|
|||
<template>
|
||||
<view class="">
|
||||
<view class="u-navbar" :style="[navbarStyle]" :class="{ 'u-navbar-fixed': isFixed, 'u-border-bottom': borderBottom }">
|
||||
<view class="u-status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
|
||||
<view class="u-navbar-inner" :style="[navbarInnerStyle]">
|
||||
<view class="u-back-wrap" v-if="isBack" @tap="goBack">
|
||||
<view class="u-icon-wrap">
|
||||
<u-icon :name="backIconName" :color="backIconColor" :size="backIconSize"></u-icon>
|
||||
</view>
|
||||
<view class="u-icon-wrap u-back-text u-line-1" v-if="backText" :style="[backTextStyle]">{{ backText }}</view>
|
||||
</view>
|
||||
<view class="u-navbar-content-title" v-if="title" :style="[titleStyle]">
|
||||
<view
|
||||
class="u-title u-line-1"
|
||||
:style="{
|
||||
color: titleColor,
|
||||
fontSize: titleSize + 'rpx',
|
||||
fontWeight: titleBold ? 'bold' : 'normal'
|
||||
}">
|
||||
{{ title }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-slot-content">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="u-slot-right">
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 解决fixed定位后导航栏塌陷的问题 -->
|
||||
<view class="u-navbar-placeholder" v-if="isFixed && !immersive" :style="{ width: '100%', height: Number(navbarHeight) + statusBarHeight + 'px' }"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 获取系统状态栏的高度
|
||||
let systemInfo = uni.getSystemInfoSync();
|
||||
let menuButtonInfo = {};
|
||||
// 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API,尚未兼容)
|
||||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
|
||||
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
|
||||
// #endif
|
||||
/**
|
||||
* navbar 自定义导航栏
|
||||
* @description 此组件一般用于在特殊情况下,需要自定义导航栏的时候用到,一般建议使用uniapp自带的导航栏。
|
||||
* @tutorial https://www.uviewui.com/components/navbar.html
|
||||
* @property {String Number} height 导航栏高度(不包括状态栏高度在内,内部自动加上),注意这里的单位是px(默认44)
|
||||
* @property {String} back-icon-color 左边返回图标的颜色(默认#606266)
|
||||
* @property {String} back-icon-name 左边返回图标的名称,只能为uView自带的图标(默认arrow-left)
|
||||
* @property {String Number} back-icon-size 左边返回图标的大小,单位rpx(默认30)
|
||||
* @property {String} back-text 返回图标右边的辅助提示文字
|
||||
* @property {Object} back-text-style 返回图标右边的辅助提示文字的样式,对象形式(默认{ color: '#606266' })
|
||||
* @property {String} title 导航栏标题,如设置为空字符,将会隐藏标题占位区域
|
||||
* @property {String Number} title-width 导航栏标题的最大宽度,内容超出会以省略号隐藏,单位rpx(默认250)
|
||||
* @property {String} title-color 标题的颜色(默认#606266)
|
||||
* @property {String Number} title-size 导航栏标题字体大小,单位rpx(默认32)
|
||||
* @property {Function} custom-back 自定义返回逻辑方法
|
||||
* @property {String Number} z-index 固定在顶部时的z-index值(默认980)
|
||||
* @property {Boolean} is-back 是否显示导航栏左边返回图标和辅助文字(默认true)
|
||||
* @property {Object} background 导航栏背景设置,见官网说明(默认{ background: '#ffffff' })
|
||||
* @property {Boolean} is-fixed 导航栏是否固定在顶部(默认true)
|
||||
* @property {Boolean} immersive 沉浸式,允许fixed定位后导航栏塌陷,仅fixed定位下生效(默认false)
|
||||
* @property {Boolean} border-bottom 导航栏底部是否显示下边框,如定义了较深的背景颜色,可取消此值(默认true)
|
||||
* @example <u-navbar back-text="返回" title="剑未配妥,出门已是江湖"></u-navbar>
|
||||
*/
|
||||
export default {
|
||||
name: "u-navbar",
|
||||
props: {
|
||||
// 导航栏高度,单位px,非rpx
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 返回箭头的颜色
|
||||
backIconColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 左边返回的图标
|
||||
backIconName: {
|
||||
type: String,
|
||||
default: 'nav-back'
|
||||
},
|
||||
// 左边返回图标的大小,rpx
|
||||
backIconSize: {
|
||||
type: [String, Number],
|
||||
default: '44'
|
||||
},
|
||||
// 返回的文字提示
|
||||
backText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 返回的文字的 样式
|
||||
backTextStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: '#606266'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 导航栏标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题的宽度,如果需要自定义右侧内容,且右侧内容很多时,可能需要减少这个宽度,单位rpx
|
||||
titleWidth: {
|
||||
type: [String, Number],
|
||||
default: '250'
|
||||
},
|
||||
// 标题的颜色
|
||||
titleColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 标题字体是否加粗
|
||||
titleBold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题的字体大小
|
||||
titleSize: {
|
||||
type: [String, Number],
|
||||
default: 32
|
||||
},
|
||||
isBack: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
// 对象形式,因为用户可能定义一个纯色,或者线性渐变的颜色
|
||||
background: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
background: '#ffffff'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 导航栏是否固定在顶部
|
||||
isFixed: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否沉浸式,允许fixed定位后导航栏塌陷,仅fixed定位下生效
|
||||
immersive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示导航栏的下边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 自定义返回逻辑
|
||||
customBack: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menuButtonInfo: menuButtonInfo,
|
||||
statusBarHeight: systemInfo.statusBarHeight
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 导航栏内部盒子的样式
|
||||
navbarInnerStyle() {
|
||||
let style = {};
|
||||
// 导航栏宽度,如果在小程序下,导航栏宽度为胶囊的左边到屏幕左边的距离
|
||||
style.height = this.navbarHeight + 'px';
|
||||
// // 如果是各家小程序,导航栏内部的宽度需要减少右边胶囊的宽度
|
||||
// #ifdef MP
|
||||
let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
|
||||
style.marginRight = rightButtonWidth + 'px';
|
||||
// #endif
|
||||
return style;
|
||||
},
|
||||
// 整个导航栏的样式
|
||||
navbarStyle() {
|
||||
let style = {};
|
||||
style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.navbar;
|
||||
// 合并用户传递的背景色对象
|
||||
Object.assign(style, this.background);
|
||||
return style;
|
||||
},
|
||||
// 导航中间的标题的样式
|
||||
titleStyle() {
|
||||
let style = {};
|
||||
// #ifndef MP
|
||||
style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
|
||||
style.right = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
// 此处是为了让标题显示区域即使在小程序有右侧胶囊的情况下也能处于屏幕的中间,是通过绝对定位实现的
|
||||
let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
|
||||
style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
|
||||
style.right = rightButtonWidth - (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + rightButtonWidth +
|
||||
'px';
|
||||
// #endif
|
||||
style.width = uni.upx2px(this.titleWidth) + 'px';
|
||||
return style;
|
||||
},
|
||||
// 转换字符数值为真正的数值
|
||||
navbarHeight() {
|
||||
// #ifdef APP-PLUS || H5
|
||||
return this.height ? this.height : 44;
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
// 小程序特别处理,让导航栏高度 = 胶囊高度 + 两倍胶囊顶部与状态栏底部的距离之差(相当于同时获得了导航栏底部与胶囊底部的距离)
|
||||
// 此方法有缺陷,暂不用(会导致少了几个px),采用直接固定值的方式
|
||||
// return menuButtonInfo.height + (menuButtonInfo.top - this.statusBarHeight) * 2;//导航高度
|
||||
let height = systemInfo.platform == 'ios' ? 44 : 48;
|
||||
return this.height ? this.height : height;
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
goBack() {
|
||||
// 如果自定义了点击返回按钮的函数,则执行,否则执行返回逻辑
|
||||
if (typeof this.customBack === 'function') {
|
||||
// 在微信,支付宝等环境(H5正常),会导致父组件定义的customBack()函数体中的this变成子组件的this
|
||||
// 通过bind()方法,绑定父组件的this,让this.customBack()的this为父组件的上下文
|
||||
this.customBack.bind(this.$u.$parent.call(this))();
|
||||
} else {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-navbar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.u-navbar-fixed {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 991;
|
||||
}
|
||||
|
||||
.u-status-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.u-navbar-inner {
|
||||
@include vue-flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-back-wrap {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
flex-grow: 0;
|
||||
padding: 14rpx 14rpx 14rpx 24rpx;
|
||||
}
|
||||
|
||||
.u-back-text {
|
||||
padding-left: 4rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.u-navbar-content-title {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 60rpx;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.u-navbar-centent-slot {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.u-title {
|
||||
line-height: 60rpx;
|
||||
font-size: 32rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.u-navbar-right {
|
||||
flex: 1;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.u-slot-content {
|
||||
flex: 1;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,272 @@
|
|||
<template>
|
||||
<view class="u-notice-bar-wrap" v-if="isShow" :style="{
|
||||
borderRadius: borderRadius + 'rpx',
|
||||
}">
|
||||
<block v-if="mode == 'horizontal' && isCircular">
|
||||
<u-row-notice
|
||||
:type="type"
|
||||
:color="color"
|
||||
:bgColor="bgColor"
|
||||
:list="list"
|
||||
:volumeIcon="volumeIcon"
|
||||
:moreIcon="moreIcon"
|
||||
:volumeSize="volumeSize"
|
||||
:closeIcon="closeIcon"
|
||||
:mode="mode"
|
||||
:fontSize="fontSize"
|
||||
:speed="speed"
|
||||
:playState="playState"
|
||||
:padding="padding"
|
||||
@getMore="getMore"
|
||||
@close="close"
|
||||
@click="click"
|
||||
></u-row-notice>
|
||||
</block>
|
||||
<block v-if="mode == 'vertical' || (mode == 'horizontal' && !isCircular)">
|
||||
<u-column-notice
|
||||
:type="type"
|
||||
:color="color"
|
||||
:bgColor="bgColor"
|
||||
:list="list"
|
||||
:volumeIcon="volumeIcon"
|
||||
:moreIcon="moreIcon"
|
||||
:closeIcon="closeIcon"
|
||||
:mode="mode"
|
||||
:volumeSize="volumeSize"
|
||||
:disable-touch="disableTouch"
|
||||
:fontSize="fontSize"
|
||||
:duration="duration"
|
||||
:playState="playState"
|
||||
:padding="padding"
|
||||
@getMore="getMore"
|
||||
@close="close"
|
||||
@click="click"
|
||||
@end="end"
|
||||
></u-column-notice>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* noticeBar 滚动通知
|
||||
* @description 该组件用于滚动通告场景,有多种模式可供选择
|
||||
* @tutorial https://www.uviewui.com/components/noticeBar.html
|
||||
* @property {Array} list 滚动内容,数组形式,见上方说明
|
||||
* @property {String} type 显示的主题(默认warning)
|
||||
* @property {Boolean} volume-icon 是否显示小喇叭图标(默认true)
|
||||
* @property {Boolean} more-icon 是否显示右边的向右箭头(默认false)
|
||||
* @property {Boolean} close-icon 是否显示关闭图标(默认false)
|
||||
* @property {Boolean} autoplay 是否自动播放(默认true)
|
||||
* @property {String} color 文字颜色
|
||||
* @property {String Number} bg-color 背景颜色
|
||||
* @property {String} mode 滚动模式(默认horizontal)
|
||||
* @property {Boolean} show 是否显示(默认true)
|
||||
* @property {String Number} font-size 字体大小,单位rpx(默认28)
|
||||
* @property {String Number} volume-size 左边喇叭的大小(默认34)
|
||||
* @property {String Number} duration 滚动周期时长,只对步进模式有效,横向衔接模式无效,单位ms(默认2000)
|
||||
* @property {String Number} speed 水平滚动时的滚动速度,即每秒移动多少距离,只对水平衔接方式有效,单位rpx(默认160)
|
||||
* @property {String Number} font-size 字体大小,单位rpx(默认28)
|
||||
* @property {Boolean} is-circular mode为horizontal时,指明是否水平衔接滚动(默认true)
|
||||
* @property {String} play-state 播放状态,play - 播放,paused - 暂停(默认play)
|
||||
* @property {String Nubmer} border-radius 通知栏圆角(默认为0)
|
||||
* @property {String Nubmer} padding 内边距,字符串,与普通的内边距css写法一直(默认"18rpx 24rpx")
|
||||
* @property {Boolean} no-list-hidden 列表为空时,是否显示组件(默认false)
|
||||
* @property {Boolean} disable-touch 是否禁止通过手动滑动切换通知,只有mode = vertical,或者mode = horizontal且is-circular = false时有效(默认true)
|
||||
* @event {Function} click 点击通告文字触发,只有mode = vertical,或者mode = horizontal且is-circular = false时有效
|
||||
* @event {Function} close 点击右侧关闭图标触发
|
||||
* @event {Function} getMore 点击右侧向右图标触发
|
||||
* @event {Function} end 列表的消息每次被播放一个周期时触发,只有mode = vertical,或者mode = horizontal且is-circular = false时有效
|
||||
* @example <u-notice-bar :more-icon="true" :list="list"></u-notice-bar>
|
||||
*/
|
||||
export default {
|
||||
name: "u-notice-bar",
|
||||
props: {
|
||||
// 显示的内容,数组
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 显示的主题,success|error|primary|info|warning
|
||||
type: {
|
||||
type: String,
|
||||
default: 'warning'
|
||||
},
|
||||
// 是否显示左侧的音量图标
|
||||
volumeIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 音量喇叭的大小
|
||||
volumeSize: {
|
||||
type: [Number, String],
|
||||
default: 34
|
||||
},
|
||||
// 是否显示右侧的右箭头图标
|
||||
moreIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示右侧的关闭图标
|
||||
closeIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 文字颜色,各图标也会使用文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 滚动方向,horizontal-水平滚动,vertical-垂直滚动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 字体大小,单位rpx
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 28
|
||||
},
|
||||
// 滚动一个周期的时间长,单位ms
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 2000
|
||||
},
|
||||
// 水平滚动时的滚动速度,即每秒滚动多少rpx,这有利于控制文字无论多少时,都能有一个恒定的速度
|
||||
speed: {
|
||||
type: [Number, String],
|
||||
default: 160
|
||||
},
|
||||
// 水平滚动时,是否采用衔接形式滚动
|
||||
// 水平衔接模式,采用的是swiper组件,水平滚动
|
||||
isCircular: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 播放状态,play-播放,paused-暂停
|
||||
playState: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 是否禁止用手滑动切换
|
||||
// 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序
|
||||
disableTouch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动通知设置圆角
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 通知的边距
|
||||
padding: {
|
||||
type: [Number, String],
|
||||
default: '18rpx 24rpx'
|
||||
},
|
||||
// list列表为空时,是否显示组件
|
||||
noListHidden: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 如果设置show为false,或者设置了noListHidden为true,且list长度又为零的话,隐藏组件
|
||||
isShow() {
|
||||
if(this.show == false || (this.noListHidden == true && this.list.length == 0)) return false;
|
||||
else return true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击通告栏
|
||||
click(index) {
|
||||
this.$emit('click', index);
|
||||
},
|
||||
// 点击关闭按钮
|
||||
close() {
|
||||
this.$emit('close');
|
||||
},
|
||||
// 点击更多箭头按钮
|
||||
getMore() {
|
||||
this.$emit('getMore');
|
||||
},
|
||||
// 滚动一个周期结束,只对垂直,或者水平步进形式有效
|
||||
end() {
|
||||
this.$emit('end');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-notice-bar-wrap {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-notice-bar {
|
||||
padding: 18rpx 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.u-direction-row {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.u-left-icon {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-notice-box {
|
||||
flex: 1;
|
||||
@include vue-flex;
|
||||
overflow: hidden;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.u-right-icon {
|
||||
margin-left: 12rpx;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-notice-content {
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
font-size: 26rpx;
|
||||
animation: u-loop-animation 10s linear infinite both;
|
||||
text-align: right;
|
||||
// 这一句很重要,为了能让滚动左右连接起来
|
||||
padding-left: 100%;
|
||||
}
|
||||
|
||||
@keyframes u-loop-animation {
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,363 @@
|
|||
<template>
|
||||
<view class="u-numberbox">
|
||||
<view class="u-icon-minus" @touchstart.stop.prevent="btnTouchStart('minus')" @touchend.stop.prevent="clearTimer" :class="{ 'u-icon-disabled': disabled || inputVal <= min }"
|
||||
:style="{
|
||||
background: bgColor,
|
||||
height: inputHeight + 'rpx',
|
||||
color: color
|
||||
}">
|
||||
<u-icon name="minus" :size="size"></u-icon>
|
||||
</view>
|
||||
<input :disabled="disabledInput || disabled" :cursor-spacing="getCursorSpacing" :class="{ 'u-input-disabled': disabled }"
|
||||
v-model="inputVal" class="u-number-input" @blur="onBlur" @focus="onFocus"
|
||||
type="number" :style="{
|
||||
color: color,
|
||||
fontSize: size + 'rpx',
|
||||
background: bgColor,
|
||||
height: inputHeight + 'rpx',
|
||||
width: inputWidth + 'rpx'
|
||||
}" />
|
||||
<view class="u-icon-plus" @touchstart.stop.prevent="btnTouchStart('plus')" @touchend.stop.prevent="clearTimer" :class="{ 'u-icon-disabled': disabled || inputVal >= max }"
|
||||
:style="{
|
||||
background: bgColor,
|
||||
height: inputHeight + 'rpx',
|
||||
color: color
|
||||
}">
|
||||
<u-icon name="plus" :size="size"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* numberBox 步进器
|
||||
* @description 该组件一般用于商城购物选择物品数量的场景。注意:该输入框只能输入大于或等于0的整数,不支持小数输入
|
||||
* @tutorial https://www.uviewui.com/components/numberBox.html
|
||||
* @property {Number} value 输入框初始值(默认1)
|
||||
* @property {String} bg-color 输入框和按钮的背景颜色(默认#F2F3F5)
|
||||
* @property {Number} min 用户可输入的最小值(默认0)
|
||||
* @property {Number} max 用户可输入的最大值(默认99999)
|
||||
* @property {Number} step 步长,每次加或减的值(默认1)
|
||||
* @property {Boolean} disabled 是否禁用操作,禁用后无法加减或手动修改输入框的值(默认false)
|
||||
* @property {Boolean} disabled-input 是否禁止输入框手动输入值(默认false)
|
||||
* @property {Boolean} positive-integer 是否只能输入正整数(默认true)
|
||||
* @property {String | Number} size 输入框文字和按钮字体大小,单位rpx(默认26)
|
||||
* @property {String} color 输入框文字和加减按钮图标的颜色(默认#323233)
|
||||
* @property {String | Number} input-width 输入框宽度,单位rpx(默认80)
|
||||
* @property {String | Number} input-height 输入框和按钮的高度,单位rpx(默认50)
|
||||
* @property {String | Number} index 事件回调时用以区分当前发生变化的是哪个输入框
|
||||
* @property {Boolean} long-press 是否开启长按连续递增或递减(默认true)
|
||||
* @property {String | Number} press-time 开启长按触发后,每触发一次需要多久,单位ms(默认250)
|
||||
* @property {String | Number} cursor-spacing 指定光标于键盘的距离,避免键盘遮挡输入框,单位rpx(默认200)
|
||||
* @event {Function} change 输入框内容发生变化时触发,对象形式
|
||||
* @event {Function} blur 输入框失去焦点时触发,对象形式
|
||||
* @event {Function} minus 点击减少按钮时触发(按钮可点击情况下),对象形式
|
||||
* @event {Function} plus 点击增加按钮时触发(按钮可点击情况下),对象形式
|
||||
* @example <u-number-box :min="1" :max="100"></u-number-box>
|
||||
*/
|
||||
export default {
|
||||
name: "u-number-box",
|
||||
props: {
|
||||
// 预显示的数字
|
||||
value: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 背景颜色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#F2F3F5'
|
||||
},
|
||||
// 最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 99999
|
||||
},
|
||||
// 步进值,每次加或减的值
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 是否禁用加减操作
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// input的字体大小,单位rpx
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
},
|
||||
// 加减图标的颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#323233'
|
||||
},
|
||||
// input宽度,单位rpx
|
||||
inputWidth: {
|
||||
type: [Number, String],
|
||||
default: 80
|
||||
},
|
||||
// input高度,单位rpx
|
||||
inputHeight: {
|
||||
type: [Number, String],
|
||||
default: 50
|
||||
},
|
||||
// index索引,用于列表中使用,让用户知道是哪个numberbox发生了变化,一般使用for循环出来的index值即可
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁用输入框,与disabled作用于输入框时,为OR的关系,即想要禁用输入框,又可以加减的话
|
||||
// 设置disabled为false,disabledInput为true即可
|
||||
disabledInput: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 输入框于键盘之间的距离
|
||||
cursorSpacing: {
|
||||
type: [Number, String],
|
||||
default: 100
|
||||
},
|
||||
// 是否开启长按连续递增或递减
|
||||
longPress: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 开启长按触发后,每触发一次需要多久
|
||||
pressTime: {
|
||||
type: [Number, String],
|
||||
default: 250
|
||||
},
|
||||
// 是否只能输入大于或等于0的整数(正整数)
|
||||
positiveInteger: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(v1, v2) {
|
||||
// 只有value的改变是来自外部的时候,才去同步inputVal的值,否则会造成循环错误
|
||||
if(!this.changeFromInner) {
|
||||
this.inputVal = v1;
|
||||
// 因为inputVal变化后,会触发this.handleChange(),在其中changeFromInner会再次被设置为true,
|
||||
// 造成外面修改值,也导致被认为是内部修改的混乱,这里进行this.$nextTick延时,保证在运行周期的最后处
|
||||
// 将changeFromInner设置为false
|
||||
this.$nextTick(function(){
|
||||
this.changeFromInner = false;
|
||||
})
|
||||
}
|
||||
},
|
||||
inputVal(v1, v2) {
|
||||
// 为了让用户能够删除所有输入值,重新输入内容,删除所有值后,内容为空字符串
|
||||
if (v1 == '') return;
|
||||
let value = 0;
|
||||
// 首先判断是否数值,并且在min和max之间,如果不是,使用原来值
|
||||
let tmp = this.$u.test.number(v1);
|
||||
if (tmp && v1 >= this.min && v1 <= this.max) value = v1;
|
||||
else value = v2;
|
||||
// 判断是否只能输入大于等于0的整数
|
||||
if(this.positiveInteger) {
|
||||
// 小于0,或者带有小数点,
|
||||
if(v1 < 0 || String(v1).indexOf('.') !== -1) {
|
||||
value = v2;
|
||||
// 双向绑定input的值,必须要使用$nextTick修改显示的值
|
||||
this.$nextTick(() => {
|
||||
this.inputVal = v2;
|
||||
})
|
||||
}
|
||||
}
|
||||
// 发出change事件
|
||||
this.handleChange(value, 'change');
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputVal: 1, // 输入框中的值,不能直接使用props中的value,因为应该改变props的状态
|
||||
timer: null, // 用作长按的定时器
|
||||
changeFromInner: false, // 值发生变化,是来自内部还是外部
|
||||
innerChangeTimer: null, // 内部定时器
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.inputVal = Number(this.value);
|
||||
},
|
||||
computed: {
|
||||
getCursorSpacing() {
|
||||
// 先将值转为px单位,再转为数值
|
||||
return Number(uni.upx2px(this.cursorSpacing));
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击退格键
|
||||
btnTouchStart(callback) {
|
||||
// 先执行一遍方法,否则会造成松开手时,就执行了clearTimer,导致无法实现功能
|
||||
this[callback]();
|
||||
// 如果没开启长按功能,直接返回
|
||||
if (!this.longPress) return;
|
||||
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
|
||||
this.timer = null;
|
||||
this.timer = setInterval(() => {
|
||||
// 执行加或减函数
|
||||
this[callback]();
|
||||
}, this.pressTime);
|
||||
},
|
||||
clearTimer() {
|
||||
this.$nextTick(() => {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
})
|
||||
},
|
||||
minus() {
|
||||
this.computeVal('minus');
|
||||
},
|
||||
plus() {
|
||||
this.computeVal('plus');
|
||||
},
|
||||
// 为了保证小数相加减出现精度溢出的问题
|
||||
calcPlus(num1, num2) {
|
||||
let baseNum, baseNum1, baseNum2;
|
||||
try {
|
||||
baseNum1 = num1.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
baseNum1 = 0;
|
||||
}
|
||||
try {
|
||||
baseNum2 = num2.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
baseNum2 = 0;
|
||||
}
|
||||
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
|
||||
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2; //精度
|
||||
return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision);
|
||||
},
|
||||
// 为了保证小数相加减出现精度溢出的问题
|
||||
calcMinus(num1, num2) {
|
||||
let baseNum, baseNum1, baseNum2;
|
||||
try {
|
||||
baseNum1 = num1.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
baseNum1 = 0;
|
||||
}
|
||||
try {
|
||||
baseNum2 = num2.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
baseNum2 = 0;
|
||||
}
|
||||
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
|
||||
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2;
|
||||
return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision);
|
||||
},
|
||||
computeVal(type) {
|
||||
uni.hideKeyboard();
|
||||
if (this.disabled) return;
|
||||
let value = 0;
|
||||
// 减
|
||||
if (type === 'minus') {
|
||||
value = this.calcMinus(this.inputVal, this.step);
|
||||
} else if (type === 'plus') {
|
||||
value = this.calcPlus(this.inputVal, this.step);
|
||||
}
|
||||
// 判断是否小于最小值和大于最大值
|
||||
if (value < this.min || value > this.max) {
|
||||
return;
|
||||
}
|
||||
this.inputVal = value;
|
||||
this.handleChange(value, type);
|
||||
},
|
||||
// 处理用户手动输入的情况
|
||||
onBlur(event) {
|
||||
let val = 0;
|
||||
let value = event.detail.value;
|
||||
// 如果为非0-9数字组成,或者其第一位数值为0,直接让其等于min值
|
||||
// 这里不直接判断是否正整数,是因为用户传递的props min值可能为0
|
||||
if (!/(^\d+$)/.test(value) || value[0] == 0) val = this.min;
|
||||
val = +value;
|
||||
if (val > this.max) {
|
||||
val = this.max;
|
||||
} else if (val < this.min) {
|
||||
val = this.min;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.inputVal = val;
|
||||
})
|
||||
this.handleChange(val, 'blur');
|
||||
},
|
||||
// 输入框获得焦点事件
|
||||
onFocus() {
|
||||
this.$emit('focus');
|
||||
},
|
||||
handleChange(value, type) {
|
||||
if (this.disabled) return;
|
||||
// 清除定时器,避免造成混乱
|
||||
if(this.innerChangeTimer) {
|
||||
clearTimeout(this.innerChangeTimer);
|
||||
this.innerChangeTimer = null;
|
||||
}
|
||||
// 发出input事件,修改通过v-model绑定的值,达到双向绑定的效果
|
||||
this.changeFromInner = true;
|
||||
// 一定时间内,清除changeFromInner标记,否则内部值改变后
|
||||
// 外部通过程序修改value值,将会无效
|
||||
this.innerChangeTimer = setTimeout(() => {
|
||||
this.changeFromInner = false;
|
||||
}, 150);
|
||||
this.$emit('input', Number(value));
|
||||
this.$emit(type, {
|
||||
// 转为Number类型
|
||||
value: Number(value),
|
||||
index: this.index
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-numberbox {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-number-input {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
margin: 0 6rpx;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.u-icon-plus,
|
||||
.u-icon-minus {
|
||||
width: 60rpx;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.u-icon-plus {
|
||||
border-radius: 0 8rpx 8rpx 0;
|
||||
}
|
||||
|
||||
.u-icon-minus {
|
||||
border-radius: 8rpx 0 0 8rpx;
|
||||
}
|
||||
|
||||
.u-icon-disabled {
|
||||
color: #c8c9cc !important;
|
||||
background: #f7f8fa !important;
|
||||
}
|
||||
|
||||
.u-input-disabled {
|
||||
color: #c8c9cc !important;
|
||||
background-color: #f2f3f5 !important;
|
||||
}
|
||||
</style>
|
158
node_modules/uview-ui/components/u-number-keyboard/u-number-keyboard.vue
generated
vendored
100644
158
node_modules/uview-ui/components/u-number-keyboard/u-number-keyboard.vue
generated
vendored
100644
|
@ -0,0 +1,158 @@
|
|||
<template>
|
||||
<view class="u-keyboard" @touchmove.stop.prevent="() => {}">
|
||||
<view class="u-keyboard-grids">
|
||||
<view
|
||||
class="u-keyboard-grids-item"
|
||||
:class="[btnBgGray(index) ? 'u-bg-gray' : '', index <= 2 ? 'u-border-top' : '', index < 9 ? 'u-border-bottom' : '', (index + 1) % 3 != 0 ? 'u-border-right' : '']"
|
||||
:style="[itemStyle(index)]"
|
||||
v-for="(item, index) in numList"
|
||||
:key="index"
|
||||
:hover-class="hoverClass(index)"
|
||||
:hover-stay-time="100"
|
||||
@tap="keyboardClick(item)">
|
||||
<view class="u-keyboard-grids-btn">{{ item }}</view>
|
||||
</view>
|
||||
<view class="u-keyboard-grids-item u-bg-gray" hover-class="u-hover-class" :hover-stay-time="100" @touchstart.stop="backspaceClick"
|
||||
@touchend="clearTimer">
|
||||
<view class="u-keyboard-back u-keyboard-grids-btn">
|
||||
<u-icon name="backspace" :size="38" :bold="true"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// 键盘的类型,number-数字键盘,card-身份证键盘
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'number'
|
||||
},
|
||||
// 是否显示键盘的"."符号
|
||||
dotEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否打乱键盘按键的顺序
|
||||
random: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
backspace: 'backspace', // 退格键内容
|
||||
dot: '.', // 点
|
||||
timer: null, // 长按多次删除的事件监听
|
||||
cardX: 'X' // 身份证的X符号
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 键盘需要显示的内容
|
||||
numList() {
|
||||
let tmp = [];
|
||||
if (!this.dotEnabled && this.mode == 'number') {
|
||||
if (!this.random) {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
|
||||
} else {
|
||||
return this.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]);
|
||||
}
|
||||
} else if (this.dotEnabled && this.mode == 'number') {
|
||||
if (!this.random) {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0];
|
||||
} else {
|
||||
return this.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]);
|
||||
}
|
||||
} else if (this.mode == 'card') {
|
||||
if (!this.random) {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0];
|
||||
} else {
|
||||
return this.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]);
|
||||
}
|
||||
}
|
||||
},
|
||||
// 按键的样式,在非乱序&&数字键盘&&不显示点按钮时,index为9时,按键占位两个空间
|
||||
itemStyle() {
|
||||
return index => {
|
||||
let style = {};
|
||||
if (this.mode == 'number' && !this.dotEnabled && index == 9) style.flex = '0 0 66.6666666666%';
|
||||
return style;
|
||||
};
|
||||
},
|
||||
// 是否让按键显示灰色,只在非乱序&&数字键盘&&且允许点按键的时候
|
||||
btnBgGray() {
|
||||
return index => {
|
||||
if (!this.random && index == 9 && (this.mode != 'number' || (this.mode == 'number' && this.dotEnabled))) return true;
|
||||
else return false;
|
||||
};
|
||||
},
|
||||
hoverClass() {
|
||||
return index => {
|
||||
if (!this.random && index == 9 && (this.mode == 'number' && this.dotEnabled || this.mode == 'card')) return 'u-hover-class';
|
||||
else return 'u-keyboard-hover';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击退格键
|
||||
backspaceClick() {
|
||||
this.$emit('backspace');
|
||||
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
|
||||
this.timer = null;
|
||||
this.timer = setInterval(() => {
|
||||
this.$emit('backspace');
|
||||
}, 250);
|
||||
},
|
||||
clearTimer() {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
},
|
||||
// 获取键盘显示的内容
|
||||
keyboardClick(val) {
|
||||
// 允许键盘显示点模式和触发非点按键时,将内容转为数字类型
|
||||
if (this.dotEnabled && val != this.dot && val != this.cardX) val = Number(val);
|
||||
this.$emit('change', val);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/style.components.scss";
|
||||
|
||||
.u-keyboard {
|
||||
position: relative;
|
||||
z-index: 1003;
|
||||
}
|
||||
|
||||
.u-keyboard-grids {
|
||||
@include vue-flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.u-keyboard-grids-item {
|
||||
flex: 0 0 33.3333333333%;
|
||||
text-align: center;
|
||||
font-size: 50rpx;
|
||||
color: #333;
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 110rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.u-bg-gray {
|
||||
background-color: $u-border-color;
|
||||
}
|
||||
|
||||
.u-keyboard-back {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.u-keyboard-hover {
|
||||
background-color: #e7e6eb;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,100 @@
|
|||
const cfg = require('./config.js'),
|
||||
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
|
||||
function CssHandler(tagStyle) {
|
||||
var styles = Object.assign(Object.create(null), cfg.userAgentStyles);
|
||||
for (var item in tagStyle)
|
||||
styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item];
|
||||
this.styles = styles;
|
||||
}
|
||||
CssHandler.prototype.getStyle = function(data) {
|
||||
this.styles = new parser(data, this.styles).parse();
|
||||
}
|
||||
CssHandler.prototype.match = function(name, attrs) {
|
||||
var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : '';
|
||||
if (attrs.class) {
|
||||
var items = attrs.class.split(' ');
|
||||
for (var i = 0, item; item = items[i]; i++)
|
||||
if (tmp = this.styles['.' + item])
|
||||
matched += tmp + ';';
|
||||
}
|
||||
if (tmp = this.styles['#' + attrs.id])
|
||||
matched += tmp + ';';
|
||||
return matched;
|
||||
}
|
||||
module.exports = CssHandler;
|
||||
|
||||
function parser(data, init) {
|
||||
this.data = data;
|
||||
this.floor = 0;
|
||||
this.i = 0;
|
||||
this.list = [];
|
||||
this.res = init;
|
||||
this.state = this.Space;
|
||||
}
|
||||
parser.prototype.parse = function() {
|
||||
for (var c; c = this.data[this.i]; this.i++)
|
||||
this.state(c);
|
||||
return this.res;
|
||||
}
|
||||
parser.prototype.section = function() {
|
||||
return this.data.substring(this.start, this.i);
|
||||
}
|
||||
// 状态机
|
||||
parser.prototype.Space = function(c) {
|
||||
if (c == '.' || c == '#' || isLetter(c)) {
|
||||
this.start = this.i;
|
||||
this.state = this.Name;
|
||||
} else if (c == '/' && this.data[this.i + 1] == '*')
|
||||
this.Comment();
|
||||
else if (!cfg.blankChar[c] && c != ';')
|
||||
this.state = this.Ignore;
|
||||
}
|
||||
parser.prototype.Comment = function() {
|
||||
this.i = this.data.indexOf('*/', this.i) + 1;
|
||||
if (!this.i) this.i = this.data.length;
|
||||
this.state = this.Space;
|
||||
}
|
||||
parser.prototype.Ignore = function(c) {
|
||||
if (c == '{') this.floor++;
|
||||
else if (c == '}' && !--this.floor) {
|
||||
this.list = [];
|
||||
this.state = this.Space;
|
||||
}
|
||||
}
|
||||
parser.prototype.Name = function(c) {
|
||||
if (cfg.blankChar[c]) {
|
||||
this.list.push(this.section());
|
||||
this.state = this.NameSpace;
|
||||
} else if (c == '{') {
|
||||
this.list.push(this.section());
|
||||
this.Content();
|
||||
} else if (c == ',') {
|
||||
this.list.push(this.section());
|
||||
this.Comma();
|
||||
} else if (!isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_')
|
||||
this.state = this.Ignore;
|
||||
}
|
||||
parser.prototype.NameSpace = function(c) {
|
||||
if (c == '{') this.Content();
|
||||
else if (c == ',') this.Comma();
|
||||
else if (!cfg.blankChar[c]) this.state = this.Ignore;
|
||||
}
|
||||
parser.prototype.Comma = function() {
|
||||
while (cfg.blankChar[this.data[++this.i]]);
|
||||
if (this.data[this.i] == '{') this.Content();
|
||||
else {
|
||||
this.start = this.i--;
|
||||
this.state = this.Name;
|
||||
}
|
||||
}
|
||||
parser.prototype.Content = function() {
|
||||
this.start = ++this.i;
|
||||
if ((this.i = this.data.indexOf('}', this.i)) == -1) this.i = this.data.length;
|
||||
var content = this.section();
|
||||
for (var i = 0, item; item = this.list[i++];)
|
||||
if (this.res[item]) this.res[item] += ';' + content;
|
||||
else this.res[item] = content;
|
||||
this.list = [];
|
||||
this.state = this.Space;
|
||||
}
|
|
@ -0,0 +1,580 @@
|
|||
/**
|
||||
* html 解析器
|
||||
* @tutorial https://github.com/jin-yufeng/Parser
|
||||
* @version 20201029
|
||||
* @author JinYufeng
|
||||
* @listens MIT
|
||||
*/
|
||||
const cfg = require('./config.js'),
|
||||
blankChar = cfg.blankChar,
|
||||
CssHandler = require('./CssHandler.js'),
|
||||
windowWidth = uni.getSystemInfoSync().windowWidth;
|
||||
var emoji;
|
||||
|
||||
function MpHtmlParser(data, options = {}) {
|
||||
this.attrs = {};
|
||||
this.CssHandler = new CssHandler(options.tagStyle, windowWidth);
|
||||
this.data = data;
|
||||
this.domain = options.domain;
|
||||
this.DOM = [];
|
||||
this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0;
|
||||
options.prot = (this.domain || '').includes('://') ? this.domain.split('://')[0] : 'http';
|
||||
this.options = options;
|
||||
this.state = this.Text;
|
||||
this.STACK = [];
|
||||
// 工具函数
|
||||
this.bubble = () => {
|
||||
for (var i = this.STACK.length, item; item = this.STACK[--i];) {
|
||||
if (cfg.richOnlyTags[item.name]) return false;
|
||||
item.c = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
this.decode = (val, amp) => {
|
||||
var i = -1,
|
||||
j, en;
|
||||
while (1) {
|
||||
if ((i = val.indexOf('&', i + 1)) == -1) break;
|
||||
if ((j = val.indexOf(';', i + 2)) == -1) break;
|
||||
if (val[i + 1] == '#') {
|
||||
en = parseInt((val[i + 2] == 'x' ? '0' : '') + val.substring(i + 2, j));
|
||||
if (!isNaN(en)) val = val.substr(0, i) + String.fromCharCode(en) + val.substr(j + 1);
|
||||
} else {
|
||||
en = val.substring(i + 1, j);
|
||||
if (cfg.entities[en] || en == amp)
|
||||
val = val.substr(0, i) + (cfg.entities[en] || '&') + val.substr(j + 1);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
this.getUrl = url => {
|
||||
if (url[0] == '/') {
|
||||
if (url[1] == '/') url = this.options.prot + ':' + url;
|
||||
else if (this.domain) url = this.domain + url;
|
||||
} else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://'))
|
||||
url = this.domain + '/' + url;
|
||||
return url;
|
||||
}
|
||||
this.isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');
|
||||
this.section = () => this.data.substring(this.start, this.i);
|
||||
this.parent = () => this.STACK[this.STACK.length - 1];
|
||||
this.siblings = () => this.STACK.length ? this.parent().children : this.DOM;
|
||||
}
|
||||
MpHtmlParser.prototype.parse = function() {
|
||||
if (emoji) this.data = emoji.parseEmoji(this.data);
|
||||
for (var c; c = this.data[this.i]; this.i++)
|
||||
this.state(c);
|
||||
if (this.state == this.Text) this.setText();
|
||||
while (this.STACK.length) this.popNode(this.STACK.pop());
|
||||
return this.DOM;
|
||||
}
|
||||
// 设置属性
|
||||
MpHtmlParser.prototype.setAttr = function() {
|
||||
var name = this.attrName.toLowerCase(),
|
||||
val = this.attrVal;
|
||||
if (cfg.boolAttrs[name]) this.attrs[name] = 'T';
|
||||
else if (val) {
|
||||
if (name == 'src' || (name == 'data-src' && !this.attrs.src)) this.attrs.src = this.getUrl(this.decode(val, 'amp'));
|
||||
else if (name == 'href' || name == 'style') this.attrs[name] = this.decode(val, 'amp');
|
||||
else if (name.substr(0, 5) != 'data-') this.attrs[name] = val;
|
||||
}
|
||||
this.attrVal = '';
|
||||
while (blankChar[this.data[this.i]]) this.i++;
|
||||
if (this.isClose()) this.setNode();
|
||||
else {
|
||||
this.start = this.i;
|
||||
this.state = this.AttrName;
|
||||
}
|
||||
}
|
||||
// 设置文本节点
|
||||
MpHtmlParser.prototype.setText = function() {
|
||||
var back, text = this.section();
|
||||
if (!text) return;
|
||||
text = (cfg.onText && cfg.onText(text, () => back = true)) || text;
|
||||
if (back) {
|
||||
this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i);
|
||||
let j = this.start + text.length;
|
||||
for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]);
|
||||
return;
|
||||
}
|
||||
if (!this.pre) {
|
||||
// 合并空白符
|
||||
var flag, tmp = [];
|
||||
for (let i = text.length, c; c = text[--i];)
|
||||
if (!blankChar[c]) {
|
||||
tmp.unshift(c);
|
||||
if (!flag) flag = 1;
|
||||
} else {
|
||||
if (tmp[0] != ' ') tmp.unshift(' ');
|
||||
if (c == '\n' && flag == void 0) flag = 0;
|
||||
}
|
||||
if (flag == 0) return;
|
||||
text = tmp.join('');
|
||||
}
|
||||
this.siblings().push({
|
||||
type: 'text',
|
||||
text: this.decode(text)
|
||||
});
|
||||
}
|
||||
// 设置元素节点
|
||||
MpHtmlParser.prototype.setNode = function() {
|
||||
var node = {
|
||||
name: this.tagName.toLowerCase(),
|
||||
attrs: this.attrs
|
||||
},
|
||||
close = cfg.selfClosingTags[node.name];
|
||||
if (this.options.nodes.length) node.type = 'node';
|
||||
this.attrs = {};
|
||||
if (!cfg.ignoreTags[node.name]) {
|
||||
// 处理属性
|
||||
var attrs = node.attrs,
|
||||
style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''),
|
||||
styleObj = {};
|
||||
if (attrs.id) {
|
||||
if (this.options.compress & 1) attrs.id = void 0;
|
||||
else if (this.options.useAnchor) this.bubble();
|
||||
}
|
||||
if ((this.options.compress & 2) && attrs.class) attrs.class = void 0;
|
||||
switch (node.name) {
|
||||
case 'a':
|
||||
case 'ad': // #ifdef APP-PLUS
|
||||
case 'iframe':
|
||||
// #endif
|
||||
this.bubble();
|
||||
break;
|
||||
case 'font':
|
||||
if (attrs.color) {
|
||||
styleObj['color'] = attrs.color;
|
||||
attrs.color = void 0;
|
||||
}
|
||||
if (attrs.face) {
|
||||
styleObj['font-family'] = attrs.face;
|
||||
attrs.face = void 0;
|
||||
}
|
||||
if (attrs.size) {
|
||||
var size = parseInt(attrs.size);
|
||||
if (size < 1) size = 1;
|
||||
else if (size > 7) size = 7;
|
||||
var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];
|
||||
styleObj['font-size'] = map[size - 1];
|
||||
attrs.size = void 0;
|
||||
}
|
||||
break;
|
||||
case 'embed':
|
||||
// #ifndef APP-PLUS
|
||||
var src = node.attrs.src || '',
|
||||
type = node.attrs.type || '';
|
||||
if (type.includes('video') || src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8'))
|
||||
node.name = 'video';
|
||||
else if (type.includes('audio') || src.includes('.m4a') || src.includes('.wav') || src.includes('.mp3') || src.includes(
|
||||
'.aac'))
|
||||
node.name = 'audio';
|
||||
else break;
|
||||
if (node.attrs.autostart)
|
||||
node.attrs.autoplay = 'T';
|
||||
node.attrs.controls = 'T';
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
this.bubble();
|
||||
break;
|
||||
// #endif
|
||||
case 'video':
|
||||
case 'audio':
|
||||
if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]);
|
||||
else this[`${node.name}Num`]++;
|
||||
if (node.name == 'video') {
|
||||
if (this.videoNum > 3)
|
||||
node.lazyLoad = 1;
|
||||
if (attrs.width) {
|
||||
styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px');
|
||||
attrs.width = void 0;
|
||||
}
|
||||
if (attrs.height) {
|
||||
styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px');
|
||||
attrs.height = void 0;
|
||||
}
|
||||
}
|
||||
if (!attrs.controls && !attrs.autoplay) attrs.controls = 'T';
|
||||
attrs.source = [];
|
||||
if (attrs.src) {
|
||||
attrs.source.push(attrs.src);
|
||||
attrs.src = void 0;
|
||||
}
|
||||
this.bubble();
|
||||
break;
|
||||
case 'td':
|
||||
case 'th':
|
||||
if (attrs.colspan || attrs.rowspan)
|
||||
for (var k = this.STACK.length, item; item = this.STACK[--k];)
|
||||
if (item.name == 'table') {
|
||||
item.flag = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attrs.align) {
|
||||
if (node.name == 'table') {
|
||||
if (attrs.align == 'center') styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto';
|
||||
else styleObj['float'] = attrs.align;
|
||||
} else styleObj['text-align'] = attrs.align;
|
||||
attrs.align = void 0;
|
||||
}
|
||||
// 压缩 style
|
||||
var styles = style.split(';');
|
||||
style = '';
|
||||
for (var i = 0, len = styles.length; i < len; i++) {
|
||||
var info = styles[i].split(':');
|
||||
if (info.length < 2) continue;
|
||||
let key = info[0].trim().toLowerCase(),
|
||||
value = info.slice(1).join(':').trim();
|
||||
if (value[0] == '-' || value.includes('safe'))
|
||||
style += `;${key}:${value}`;
|
||||
else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import'))
|
||||
styleObj[key] = value;
|
||||
}
|
||||
if (node.name == 'img') {
|
||||
if (attrs.src && !attrs.ignore) {
|
||||
if (this.bubble())
|
||||
attrs.i = (this.imgNum++).toString();
|
||||
else attrs.ignore = 'T';
|
||||
}
|
||||
if (attrs.ignore) {
|
||||
style += ';-webkit-touch-callout:none';
|
||||
styleObj['max-width'] = '100%';
|
||||
}
|
||||
var width;
|
||||
if (styleObj.width) width = styleObj.width;
|
||||
else if (attrs.width) width = attrs.width.includes('%') ? attrs.width : parseFloat(attrs.width) + 'px';
|
||||
if (width) {
|
||||
styleObj.width = width;
|
||||
attrs.width = '100%';
|
||||
if (parseInt(width) > windowWidth) {
|
||||
styleObj.height = '';
|
||||
if (attrs.height) attrs.height = void 0;
|
||||
}
|
||||
}
|
||||
if (styleObj.height) {
|
||||
attrs.height = styleObj.height;
|
||||
styleObj.height = '';
|
||||
} else if (attrs.height && !attrs.height.includes('%'))
|
||||
attrs.height = parseFloat(attrs.height) + 'px';
|
||||
}
|
||||
for (var key in styleObj) {
|
||||
var value = styleObj[key];
|
||||
if (!value) continue;
|
||||
if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1;
|
||||
// 填充链接
|
||||
if (value.includes('url')) {
|
||||
var j = value.indexOf('(');
|
||||
if (j++ != -1) {
|
||||
while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++;
|
||||
value = value.substr(0, j) + this.getUrl(value.substr(j));
|
||||
}
|
||||
}
|
||||
// 转换 rpx
|
||||
else if (value.includes('rpx'))
|
||||
value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px');
|
||||
else if (key == 'white-space' && value.includes('pre') && !close)
|
||||
this.pre = node.pre = true;
|
||||
style += `;${key}:${value}`;
|
||||
}
|
||||
style = style.substr(1);
|
||||
if (style) attrs.style = style;
|
||||
if (!close) {
|
||||
node.children = [];
|
||||
if (node.name == 'pre' && cfg.highlight) {
|
||||
this.remove(node);
|
||||
this.pre = node.pre = true;
|
||||
}
|
||||
this.siblings().push(node);
|
||||
this.STACK.push(node);
|
||||
} else if (!cfg.filter || cfg.filter(node, this) != false)
|
||||
this.siblings().push(node);
|
||||
} else {
|
||||
if (!close) this.remove(node);
|
||||
else if (node.name == 'source') {
|
||||
var parent = this.parent();
|
||||
if (parent && (parent.name == 'video' || parent.name == 'audio') && node.attrs.src)
|
||||
parent.attrs.source.push(node.attrs.src);
|
||||
} else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href;
|
||||
}
|
||||
if (this.data[this.i] == '/') this.i++;
|
||||
this.start = this.i + 1;
|
||||
this.state = this.Text;
|
||||
}
|
||||
// 移除标签
|
||||
MpHtmlParser.prototype.remove = function(node) {
|
||||
var name = node.name,
|
||||
j = this.i;
|
||||
// 处理 svg
|
||||
var handleSvg = () => {
|
||||
var src = this.data.substring(j, this.i + 1);
|
||||
node.attrs.xmlns = 'http://www.w3.org/2000/svg';
|
||||
for (var key in node.attrs) {
|
||||
if (key == 'viewbox') src = ` viewBox="${node.attrs.viewbox}"` + src;
|
||||
else if (key != 'style') src = ` ${key}="${node.attrs[key]}"` + src;
|
||||
}
|
||||
src = '<svg' + src;
|
||||
var parent = this.parent();
|
||||
if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline'))
|
||||
parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style;
|
||||
this.siblings().push({
|
||||
name: 'img',
|
||||
attrs: {
|
||||
src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
|
||||
style: node.attrs.style,
|
||||
ignore: 'T'
|
||||
}
|
||||
})
|
||||
}
|
||||
if (node.name == 'svg' && this.data[j] == '/') return handleSvg(this.i++);
|
||||
while (1) {
|
||||
if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) {
|
||||
if (name == 'pre' || name == 'svg') this.i = j;
|
||||
else this.i = this.data.length;
|
||||
return;
|
||||
}
|
||||
this.start = (this.i += 2);
|
||||
while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++;
|
||||
if (this.section().toLowerCase() == name) {
|
||||
// 代码块高亮
|
||||
if (name == 'pre') {
|
||||
this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) + this.data
|
||||
.substr(this.i - 5);
|
||||
return this.i = j;
|
||||
} else if (name == 'style')
|
||||
this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7));
|
||||
else if (name == 'title')
|
||||
this.DOM.title = this.data.substring(j + 1, this.i - 7);
|
||||
if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length;
|
||||
if (name == 'svg') handleSvg();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 节点出栈处理
|
||||
MpHtmlParser.prototype.popNode = function(node) {
|
||||
// 空白符处理
|
||||
if (node.pre) {
|
||||
node.pre = this.pre = void 0;
|
||||
for (let i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].pre)
|
||||
this.pre = true;
|
||||
}
|
||||
var siblings = this.siblings(),
|
||||
len = siblings.length,
|
||||
childs = node.children;
|
||||
if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false))
|
||||
return siblings.pop();
|
||||
var attrs = node.attrs;
|
||||
// 替换一些标签名
|
||||
if (cfg.blockTags[node.name]) node.name = 'div';
|
||||
else if (!cfg.trustTags[node.name]) node.name = 'span';
|
||||
// 处理列表
|
||||
if (node.c && (node.name == 'ul' || node.name == 'ol')) {
|
||||
if ((node.attrs.style || '').includes('list-style:none')) {
|
||||
for (let i = 0, child; child = childs[i++];)
|
||||
if (child.name == 'li')
|
||||
child.name = 'div';
|
||||
} else if (node.name == 'ul') {
|
||||
var floor = 1;
|
||||
for (let i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].name == 'ul') floor++;
|
||||
if (floor != 1)
|
||||
for (let i = childs.length; i--;)
|
||||
childs[i].floor = floor;
|
||||
} else {
|
||||
for (let i = 0, num = 1, child; child = childs[i++];)
|
||||
if (child.name == 'li') {
|
||||
child.type = 'ol';
|
||||
child.num = ((num, type) => {
|
||||
if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26);
|
||||
if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26);
|
||||
if (type == 'i' || type == 'I') {
|
||||
num = (num - 1) % 99 + 1;
|
||||
var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
|
||||
ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],
|
||||
res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || '');
|
||||
if (type == 'i') return res.toLowerCase();
|
||||
return res;
|
||||
}
|
||||
return num;
|
||||
})(num++, attrs.type) + '.';
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理表格
|
||||
if (node.name == 'table') {
|
||||
var padding = parseFloat(attrs.cellpadding),
|
||||
spacing = parseFloat(attrs.cellspacing),
|
||||
border = parseFloat(attrs.border);
|
||||
if (node.c) {
|
||||
if (isNaN(padding)) padding = 2;
|
||||
if (isNaN(spacing)) spacing = 2;
|
||||
}
|
||||
if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;
|
||||
if (node.flag && node.c) {
|
||||
// 有 colspan 或 rowspan 且含有链接的表格转为 grid 布局实现
|
||||
attrs.style = `${attrs.style || ''};${spacing ? `;grid-gap:${spacing}px` : ';border-left:0;border-top:0'}`;
|
||||
var row = 1,
|
||||
col = 1,
|
||||
colNum,
|
||||
trs = [],
|
||||
children = [],
|
||||
map = {};
|
||||
(function f(ns) {
|
||||
for (var i = 0; i < ns.length; i++) {
|
||||
if (ns[i].name == 'tr') trs.push(ns[i]);
|
||||
else f(ns[i].children || []);
|
||||
}
|
||||
})(node.children)
|
||||
for (let i = 0; i < trs.length; i++) {
|
||||
for (let j = 0, td; td = trs[i].children[j]; j++) {
|
||||
if (td.name == 'td' || td.name == 'th') {
|
||||
while (map[row + '.' + col]) col++;
|
||||
var cell = {
|
||||
name: 'div',
|
||||
c: 1,
|
||||
attrs: {
|
||||
style: (td.attrs.style || '') + (border ? `;border:${border}px solid gray` + (spacing ? '' :
|
||||
';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '')
|
||||
},
|
||||
children: td.children
|
||||
}
|
||||
if (td.attrs.colspan) {
|
||||
cell.attrs.style += ';grid-column-start:' + col + ';grid-column-end:' + (col + parseInt(td.attrs.colspan));
|
||||
if (!td.attrs.rowspan) cell.attrs.style += ';grid-row-start:' + row + ';grid-row-end:' + (row + 1);
|
||||
col += parseInt(td.attrs.colspan) - 1;
|
||||
}
|
||||
if (td.attrs.rowspan) {
|
||||
cell.attrs.style += ';grid-row-start:' + row + ';grid-row-end:' + (row + parseInt(td.attrs.rowspan));
|
||||
if (!td.attrs.colspan) cell.attrs.style += ';grid-column-start:' + col + ';grid-column-end:' + (col + 1);
|
||||
for (var k = 1; k < td.attrs.rowspan; k++) map[(row + k) + '.' + col] = 1;
|
||||
}
|
||||
children.push(cell);
|
||||
col++;
|
||||
}
|
||||
}
|
||||
if (!colNum) {
|
||||
colNum = col - 1;
|
||||
attrs.style += `;grid-template-columns:repeat(${colNum},auto)`
|
||||
}
|
||||
col = 1;
|
||||
row++;
|
||||
}
|
||||
node.children = children;
|
||||
} else {
|
||||
attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`;
|
||||
if (border || padding)
|
||||
(function f(ns) {
|
||||
for (var i = 0, n; n = ns[i]; i++) {
|
||||
if (n.name == 'th' || n.name == 'td') {
|
||||
if (border) n.attrs.style = `border:${border}px solid gray;${n.attrs.style || ''}`;
|
||||
if (padding) n.attrs.style = `padding:${padding}px;${n.attrs.style || ''}`;
|
||||
} else f(n.children || []);
|
||||
}
|
||||
})(childs)
|
||||
}
|
||||
if (this.options.autoscroll) {
|
||||
var table = Object.assign({}, node);
|
||||
node.name = 'div';
|
||||
node.attrs = {
|
||||
style: 'overflow:scroll'
|
||||
}
|
||||
node.children = [table];
|
||||
}
|
||||
}
|
||||
this.CssHandler.pop && this.CssHandler.pop(node);
|
||||
// 自动压缩
|
||||
if (node.name == 'div' && !Object.keys(attrs).length && childs.length == 1 && childs[0].name == 'div')
|
||||
siblings[len - 1] = childs[0];
|
||||
}
|
||||
// 状态机
|
||||
MpHtmlParser.prototype.Text = function(c) {
|
||||
if (c == '<') {
|
||||
var next = this.data[this.i + 1],
|
||||
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
if (isLetter(next)) {
|
||||
this.setText();
|
||||
this.start = this.i + 1;
|
||||
this.state = this.TagName;
|
||||
} else if (next == '/') {
|
||||
this.setText();
|
||||
if (isLetter(this.data[++this.i + 1])) {
|
||||
this.start = this.i + 1;
|
||||
this.state = this.EndTag;
|
||||
} else this.Comment();
|
||||
} else if (next == '!' || next == '?') {
|
||||
this.setText();
|
||||
this.Comment();
|
||||
}
|
||||
}
|
||||
}
|
||||
MpHtmlParser.prototype.Comment = function() {
|
||||
var key;
|
||||
if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->';
|
||||
else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>';
|
||||
else key = '>';
|
||||
if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length;
|
||||
else this.i += key.length - 1;
|
||||
this.start = this.i + 1;
|
||||
this.state = this.Text;
|
||||
}
|
||||
MpHtmlParser.prototype.TagName = function(c) {
|
||||
if (blankChar[c]) {
|
||||
this.tagName = this.section();
|
||||
while (blankChar[this.data[this.i]]) this.i++;
|
||||
if (this.isClose()) this.setNode();
|
||||
else {
|
||||
this.start = this.i;
|
||||
this.state = this.AttrName;
|
||||
}
|
||||
} else if (this.isClose()) {
|
||||
this.tagName = this.section();
|
||||
this.setNode();
|
||||
}
|
||||
}
|
||||
MpHtmlParser.prototype.AttrName = function(c) {
|
||||
if (c == '=' || blankChar[c] || this.isClose()) {
|
||||
this.attrName = this.section();
|
||||
if (blankChar[c])
|
||||
while (blankChar[this.data[++this.i]]);
|
||||
if (this.data[this.i] == '=') {
|
||||
while (blankChar[this.data[++this.i]]);
|
||||
this.start = this.i--;
|
||||
this.state = this.AttrValue;
|
||||
} else this.setAttr();
|
||||
}
|
||||
}
|
||||
MpHtmlParser.prototype.AttrValue = function(c) {
|
||||
if (c == '"' || c == "'") {
|
||||
this.start++;
|
||||
if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length;
|
||||
this.attrVal = this.section();
|
||||
this.i++;
|
||||
} else {
|
||||
for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++);
|
||||
this.attrVal = this.section();
|
||||
}
|
||||
this.setAttr();
|
||||
}
|
||||
MpHtmlParser.prototype.EndTag = function(c) {
|
||||
if (blankChar[c] || c == '>' || c == '/') {
|
||||
var name = this.section().toLowerCase();
|
||||
for (var i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].name == name) break;
|
||||
if (i != -1) {
|
||||
var node;
|
||||
while ((node = this.STACK.pop()).name != name) this.popNode(node);
|
||||
this.popNode(node);
|
||||
} else if (name == 'p' || name == 'br')
|
||||
this.siblings().push({
|
||||
name,
|
||||
attrs: {}
|
||||
});
|
||||
this.i = this.data.indexOf('>', this.i);
|
||||
this.start = this.i + 1;
|
||||
if (this.i == -1) this.i = this.data.length;
|
||||
else this.state = this.Text;
|
||||
}
|
||||
}
|
||||
module.exports = MpHtmlParser;
|
|
@ -0,0 +1,80 @@
|
|||
/* 配置文件 */
|
||||
var cfg = {
|
||||
// 出错占位图
|
||||
errorImg: null,
|
||||
// 过滤器函数
|
||||
filter: null,
|
||||
// 代码高亮函数
|
||||
highlight: null,
|
||||
// 文本处理函数
|
||||
onText: null,
|
||||
// 实体编码列表
|
||||
entities: {
|
||||
quot: '"',
|
||||
apos: "'",
|
||||
semi: ';',
|
||||
nbsp: '\xA0',
|
||||
ensp: '\u2002',
|
||||
emsp: '\u2003',
|
||||
ndash: '–',
|
||||
mdash: '—',
|
||||
middot: '·',
|
||||
lsquo: '‘',
|
||||
rsquo: '’',
|
||||
ldquo: '“',
|
||||
rdquo: '”',
|
||||
bull: '•',
|
||||
hellip: '…'
|
||||
},
|
||||
blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'),
|
||||
boolAttrs: makeMap('allowfullscreen,autoplay,autostart,controls,ignore,loop,muted'),
|
||||
// 块级标签,将被转为 div
|
||||
blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
|
||||
// 将被移除的标签
|
||||
ignoreTags: makeMap('area,base,canvas,frame,iframe,input,link,map,meta,param,script,source,style,svg,textarea,title,track,wbr'),
|
||||
// 只能被 rich-text 显示的标签
|
||||
richOnlyTags: makeMap('a,colgroup,fieldset,legend'),
|
||||
// 自闭合的标签
|
||||
selfClosingTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
|
||||
// 信任的标签
|
||||
trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
|
||||
// 默认的标签样式
|
||||
userAgentStyles: {
|
||||
address: 'font-style:italic',
|
||||
big: 'display:inline;font-size:1.2em',
|
||||
blockquote: 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px',
|
||||
caption: 'display:table-caption;text-align:center',
|
||||
center: 'text-align:center',
|
||||
cite: 'font-style:italic',
|
||||
dd: 'margin-left:40px',
|
||||
mark: 'background-color:yellow',
|
||||
pre: 'font-family:monospace;white-space:pre;overflow:scroll',
|
||||
s: 'text-decoration:line-through',
|
||||
small: 'display:inline;font-size:0.8em',
|
||||
u: 'text-decoration:underline'
|
||||
}
|
||||
}
|
||||
|
||||
function makeMap(str) {
|
||||
var map = Object.create(null),
|
||||
list = str.split(',');
|
||||
for (var i = list.length; i--;)
|
||||
map[list[i]] = true;
|
||||
return map;
|
||||
}
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
if (wx.canIUse('editor')) {
|
||||
cfg.blockTags.pre = void 0;
|
||||
cfg.ignoreTags.rp = true;
|
||||
Object.assign(cfg.richOnlyTags, makeMap('bdi,bdo,caption,rt,ruby'));
|
||||
Object.assign(cfg.trustTags, makeMap('bdi,bdo,caption,pre,rt,ruby'));
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
cfg.ignoreTags.iframe = void 0;
|
||||
Object.assign(cfg.trustTags, makeMap('embed,iframe'));
|
||||
// #endif
|
||||
|
||||
module.exports = cfg;
|
|
@ -0,0 +1,22 @@
|
|||
var inline = {
|
||||
abbr: 1,
|
||||
b: 1,
|
||||
big: 1,
|
||||
code: 1,
|
||||
del: 1,
|
||||
em: 1,
|
||||
i: 1,
|
||||
ins: 1,
|
||||
label: 1,
|
||||
q: 1,
|
||||
small: 1,
|
||||
span: 1,
|
||||
strong: 1,
|
||||
sub: 1,
|
||||
sup: 1
|
||||
}
|
||||
module.exports = {
|
||||
use: function(item) {
|
||||
return !item.c && !inline[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,505 @@
|
|||
<template>
|
||||
<view :class="'interlayer '+(c||'')" :style="s">
|
||||
<block v-for="(n, i) in nodes" v-bind:key="i">
|
||||
<!--图片-->
|
||||
<view v-if="n.name=='img'" :class="'_img '+n.attrs.class" :style="n.attrs.style" :data-attrs="n.attrs" @tap.stop="imgtap">
|
||||
<rich-text v-if="ctrl[i]!=0" :nodes="[{attrs:{src:loading&&(ctrl[i]||0)<2?loading:(lazyLoad&&!ctrl[i]?placeholder:(ctrl[i]==3?errorImg:n.attrs.src||'')),alt:n.attrs.alt||'',width:n.attrs.width||'',style:'-webkit-touch-callout:none;max-width:100%;display:block'+(n.attrs.height?';height:'+n.attrs.height:'')},name:'img'}]" />
|
||||
<image class="_image" :src="lazyLoad&&!ctrl[i]?placeholder:n.attrs.src" :lazy-load="lazyLoad"
|
||||
:show-menu-by-longpress="!n.attrs.ignore" :data-i="i" :data-index="n.attrs.i" data-source="img" @load="loadImg"
|
||||
@error="error" />
|
||||
</view>
|
||||
<!--文本-->
|
||||
<text v-else-if="n.type=='text'" decode>{{n.text}}</text>
|
||||
<!--#ifndef MP-BAIDU-->
|
||||
<text v-else-if="n.name=='br'">\n</text>
|
||||
<!--#endif-->
|
||||
<!--视频-->
|
||||
<view v-else-if="((n.lazyLoad&&!n.attrs.autoplay)||(n.name=='video'&&!loadVideo))&&ctrl[i]==undefined" :id="n.attrs.id"
|
||||
:class="'_video '+(n.attrs.class||'')" :style="n.attrs.style" :data-i="i" @tap.stop="_loadVideo" />
|
||||
<video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||ctrl[i]==0"
|
||||
:controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[ctrl[i]||0]"
|
||||
:unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" :data-i="i" data-source="video" @error="error" @play="play" />
|
||||
<!--音频-->
|
||||
<audio v-else-if="n.name=='audio'" :ref="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author"
|
||||
:autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster"
|
||||
:src="n.attrs.source[ctrl[i]||0]" :data-i="i" :data-id="n.attrs.id" data-source="audio" @error.native="error"
|
||||
@play.native="play" />
|
||||
<!--链接-->
|
||||
<view v-else-if="n.name=='a'" :id="n.attrs.id" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style"
|
||||
:data-attrs="n.attrs" @tap.stop="linkpress">
|
||||
<trees class="_span" c="_span" :nodes="n.children" />
|
||||
</view>
|
||||
<!--广告-->
|
||||
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']" :appid="n.attrs.appid" :apid="n.attrs.apid" :type="n.attrs.type" :adpid="n.attrs.adpid" data-source="ad" @error="error" />-->
|
||||
<!--列表-->
|
||||
<view v-else-if="n.name=='li'" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:flex;flex-direction:row'">
|
||||
<view v-if="n.type=='ol'" class="_ol-bef">{{n.num}}</view>
|
||||
<view v-else class="_ul-bef">
|
||||
<view v-if="n.floor%3==0" class="_ul-p1">█</view>
|
||||
<view v-else-if="n.floor%3==2" class="_ul-p2" />
|
||||
<view v-else class="_ul-p1" style="border-radius:50%">█</view>
|
||||
</view>
|
||||
<trees class="_li" c="_li" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
|
||||
</view>
|
||||
<!--表格-->
|
||||
<view v-else-if="n.name=='table'&&n.c&&n.flag" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:grid'">
|
||||
<trees v-for="(cell,n) in n.children" v-bind:key="n" :class="cell.attrs.class" :c="cell.attrs.class" :style="cell.attrs.style"
|
||||
:s="cell.attrs.style" :nodes="cell.children" />
|
||||
</view>
|
||||
<view v-else-if="n.name=='table'&&n.c" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:table'">
|
||||
<view v-for="(tbody, o) in n.children" v-bind:key="o" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')">
|
||||
<view v-for="(tr, p) in tbody.children" v-bind:key="p" :class="tr.attrs.class" :style="(tr.attrs.style||'')+(tr.name[0]=='t'?';display:table-'+(tr.name=='tr'?'row':'cell'):'')">
|
||||
<trees v-if="tr.name=='td'" :nodes="tr.children" />
|
||||
<trees v-else v-for="(td, q) in tr.children" v-bind:key="q" :class="td.attrs.class" :c="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')"
|
||||
:s="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')" :nodes="td.children" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!--#ifdef APP-PLUS-->
|
||||
<iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder"
|
||||
:width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
|
||||
<embed v-else-if="n.name=='embed'" :style="n.attrs.style" :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
|
||||
<!--#endif-->
|
||||
<!--富文本-->
|
||||
<!--#ifdef MP-WEIXIN || MP-QQ || APP-PLUS-->
|
||||
<rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" />
|
||||
<!--#endif-->
|
||||
<!--#ifndef MP-WEIXIN || MP-QQ || APP-PLUS-->
|
||||
<rich-text v-else-if="!n.c" :id="n.attrs.id" :nodes="[n]" style="display:inline" />
|
||||
<!--#endif-->
|
||||
<trees v-else :class="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" :c="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')"
|
||||
:style="n.attrs.style" :s="n.attrs.style" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
<script module="handler" lang="wxs" src="./handler.wxs"></script>
|
||||
<script>
|
||||
global.Parser = {};
|
||||
import trees from './trees'
|
||||
const errorImg = require('../libs/config.js').errorImg;
|
||||
export default {
|
||||
components: {
|
||||
trees
|
||||
},
|
||||
name: 'trees',
|
||||
data() {
|
||||
return {
|
||||
ctrl: [],
|
||||
placeholder: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="225"/>',
|
||||
errorImg,
|
||||
loadVideo: typeof plus == 'undefined',
|
||||
// #ifndef MP-ALIPAY
|
||||
c: '',
|
||||
s: ''
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
props: {
|
||||
nodes: Array,
|
||||
lazyLoad: Boolean,
|
||||
loading: String,
|
||||
// #ifdef MP-ALIPAY
|
||||
c: String,
|
||||
s: String
|
||||
// #endif
|
||||
},
|
||||
mounted() {
|
||||
for (this.top = this.$parent; this.top.$options.name != 'parser'; this.top = this.top.$parent);
|
||||
this.init();
|
||||
},
|
||||
// #ifdef APP-PLUS
|
||||
beforeDestroy() {
|
||||
this.observer && this.observer.disconnect();
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
init() {
|
||||
for (var i = this.nodes.length, n; n = this.nodes[--i];) {
|
||||
if (n.name == 'img') {
|
||||
this.top.imgList.setItem(n.attrs.i, n.attrs['original-src'] || n.attrs.src);
|
||||
// #ifdef APP-PLUS
|
||||
if (this.lazyLoad && !this.observer) {
|
||||
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
|
||||
top: 500,
|
||||
bottom: 500
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.observer.observe('._img', res => {
|
||||
if (res.intersectionRatio) {
|
||||
for (var j = this.nodes.length; j--;)
|
||||
if (this.nodes[j].name == 'img')
|
||||
this.$set(this.ctrl, j, 1);
|
||||
this.observer.disconnect();
|
||||
}
|
||||
})
|
||||
}, 0)
|
||||
}
|
||||
// #endif
|
||||
} else if (n.name == 'video' || n.name == 'audio') {
|
||||
var ctx;
|
||||
if (n.name == 'video') {
|
||||
ctx = uni.createVideoContext(n.attrs.id
|
||||
// #ifndef MP-BAIDU
|
||||
, this
|
||||
// #endif
|
||||
);
|
||||
} else if (this.$refs[n.attrs.id])
|
||||
ctx = this.$refs[n.attrs.id][0];
|
||||
if (ctx) {
|
||||
ctx.id = n.attrs.id;
|
||||
this.top.videoContexts.push(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
// #ifdef APP-PLUS
|
||||
// APP 上避免 video 错位需要延时渲染
|
||||
setTimeout(() => {
|
||||
this.loadVideo = true;
|
||||
}, 1000)
|
||||
// #endif
|
||||
},
|
||||
play(e) {
|
||||
var contexts = this.top.videoContexts;
|
||||
if (contexts.length > 1 && this.top.autopause)
|
||||
for (var i = contexts.length; i--;)
|
||||
if (contexts[i].id != e.currentTarget.dataset.id)
|
||||
contexts[i].pause();
|
||||
},
|
||||
imgtap(e) {
|
||||
var attrs = e.currentTarget.dataset.attrs;
|
||||
if (!attrs.ignore) {
|
||||
var preview = true,
|
||||
data = {
|
||||
id: e.target.id,
|
||||
src: attrs.src,
|
||||
ignore: () => preview = false
|
||||
};
|
||||
global.Parser.onImgtap && global.Parser.onImgtap(data);
|
||||
this.top.$emit('imgtap', data);
|
||||
if (preview) {
|
||||
var urls = this.top.imgList,
|
||||
current = urls[attrs.i] ? parseInt(attrs.i) : (urls = [attrs.src], 0);
|
||||
uni.previewImage({
|
||||
current,
|
||||
urls
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
loadImg(e) {
|
||||
var i = e.currentTarget.dataset.i;
|
||||
if (this.lazyLoad && !this.ctrl[i]) {
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
this.$set(this.ctrl, i, 0);
|
||||
this.$nextTick(function() {
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
this.$set(this.ctrl, i, 1);
|
||||
// #endif
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
})
|
||||
// #endif
|
||||
} else if (this.loading && this.ctrl[i] != 2) {
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
this.$set(this.ctrl, i, 0);
|
||||
this.$nextTick(function() {
|
||||
// #endif
|
||||
this.$set(this.ctrl, i, 2);
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
linkpress(e) {
|
||||
var jump = true,
|
||||
attrs = e.currentTarget.dataset.attrs;
|
||||
attrs.ignore = () => jump = false;
|
||||
global.Parser.onLinkpress && global.Parser.onLinkpress(attrs);
|
||||
this.top.$emit('linkpress', attrs);
|
||||
if (jump) {
|
||||
// #ifdef MP
|
||||
if (attrs['app-id']) {
|
||||
return uni.navigateToMiniProgram({
|
||||
appId: attrs['app-id'],
|
||||
path: attrs.path
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
if (attrs.href) {
|
||||
if (attrs.href[0] == '#') {
|
||||
if (this.top.useAnchor)
|
||||
this.top.navigateTo({
|
||||
id: attrs.href.substring(1)
|
||||
})
|
||||
} else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0) {
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openWeb(attrs.href);
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
uni.setClipboardData({
|
||||
data: attrs.href,
|
||||
success: () =>
|
||||
uni.showToast({
|
||||
title: '链接已复制'
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
} else
|
||||
uni.navigateTo({
|
||||
url: attrs.href,
|
||||
fail() {
|
||||
uni.switchTab({
|
||||
url: attrs.href,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
error(e) {
|
||||
var target = e.currentTarget,
|
||||
source = target.dataset.source,
|
||||
i = target.dataset.i;
|
||||
if (source == 'video' || source == 'audio') {
|
||||
// 加载其他 source
|
||||
var index = this.ctrl[i] ? this.ctrl[i].i + 1 : 1;
|
||||
if (index < this.nodes[i].attrs.source.length)
|
||||
this.$set(this.ctrl, i, index);
|
||||
if (e.detail.__args__)
|
||||
e.detail = e.detail.__args__[0];
|
||||
} else if (errorImg && source == 'img') {
|
||||
this.top.imgList.setItem(target.dataset.index, errorImg);
|
||||
this.$set(this.ctrl, i, 3);
|
||||
}
|
||||
this.top && this.top.$emit('error', {
|
||||
source,
|
||||
target,
|
||||
errMsg: e.detail.errMsg
|
||||
});
|
||||
},
|
||||
_loadVideo(e) {
|
||||
this.$set(this.ctrl, e.target.dataset.i, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 在这里引入自定义样式 */
|
||||
|
||||
/* 链接和图片效果 */
|
||||
._a {
|
||||
display: inline;
|
||||
padding: 1.5px 0 1.5px 0;
|
||||
color: #366092;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
._hover {
|
||||
text-decoration: underline;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
._img {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
:host {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifndef MP-ALIPAY || APP-PLUS */
|
||||
.interlayer {
|
||||
display: inherit;
|
||||
flex-direction: inherit;
|
||||
flex-wrap: inherit;
|
||||
align-content: inherit;
|
||||
align-items: inherit;
|
||||
justify-content: inherit;
|
||||
width: 100%;
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
._b,
|
||||
._strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* #ifndef MP-ALIPAY */
|
||||
._blockquote,
|
||||
._div,
|
||||
._p,
|
||||
._ol,
|
||||
._ul,
|
||||
._li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
._code {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
._del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
._em,
|
||||
._i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
._h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
._h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
._h3 {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
|
||||
._h5 {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
|
||||
._h6 {
|
||||
font-size: 0.67em;
|
||||
}
|
||||
|
||||
._h1,
|
||||
._h2,
|
||||
._h3,
|
||||
._h4,
|
||||
._h5,
|
||||
._h6 {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
._image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 360px;
|
||||
margin-top: -360px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
._ins {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
._li {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
._ol-bef {
|
||||
width: 36px;
|
||||
margin-right: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
._ul-bef {
|
||||
display: block;
|
||||
margin: 0 12px 0 23px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
._ol-bef,
|
||||
._ul-bef {
|
||||
flex: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
._ul-p1 {
|
||||
display: inline-block;
|
||||
width: 0.3em;
|
||||
height: 0.3em;
|
||||
overflow: hidden;
|
||||
line-height: 0.3em;
|
||||
}
|
||||
|
||||
._ul-p2 {
|
||||
display: inline-block;
|
||||
width: 0.23em;
|
||||
height: 0.23em;
|
||||
border: 0.05em solid black;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
._q::before {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._q::after {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._sub {
|
||||
font-size: smaller;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
._sup {
|
||||
font-size: smaller;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
/* #ifdef MP-ALIPAY || APP-PLUS || QUICKAPP-WEBVIEW */
|
||||
._abbr,
|
||||
._b,
|
||||
._code,
|
||||
._del,
|
||||
._em,
|
||||
._i,
|
||||
._ins,
|
||||
._label,
|
||||
._q,
|
||||
._span,
|
||||
._strong,
|
||||
._sub,
|
||||
._sup {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef MP-WEIXIN || MP-QQ */
|
||||
.__bdo,
|
||||
.__bdi,
|
||||
.__ruby,
|
||||
.__rt {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
._video {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 300px;
|
||||
height: 225px;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
._video::after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -15px 0 0 -15px;
|
||||
content: '';
|
||||
border-color: transparent transparent transparent white;
|
||||
border-style: solid;
|
||||
border-width: 15px 0 15px 30px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,645 @@
|
|||
<template>
|
||||
<view>
|
||||
<slot v-if="!nodes.length" />
|
||||
<!--#ifdef APP-PLUS-NVUE-->
|
||||
<web-view id="_top" ref="web" :style="'margin-top:-2px;height:'+height+'px'" @onPostMessage="_message" />
|
||||
<!--#endif-->
|
||||
<!--#ifndef APP-PLUS-NVUE-->
|
||||
<view id="_top" :style="showAm+(selectable?';user-select:text;-webkit-user-select:text':'')">
|
||||
<!--#ifdef H5 || MP-360-->
|
||||
<div :id="'rtf'+uid"></div>
|
||||
<!--#endif-->
|
||||
<!--#ifndef H5 || MP-360-->
|
||||
<trees :nodes="nodes" :lazyLoad="lazyLoad" :loading="loadingImg" />
|
||||
<!--#endif-->
|
||||
</view>
|
||||
<!--#endif-->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var search;
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
import trees from './libs/trees';
|
||||
var cache = {},
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
fs = uni.getFileSystemManager ? uni.getFileSystemManager() : null,
|
||||
// #endif
|
||||
Parser = require('./libs/MpHtmlParser.js');
|
||||
var dom;
|
||||
// 计算 cache 的 key
|
||||
function hash(str) {
|
||||
for (var i = str.length, val = 5381; i--;)
|
||||
val += (val << 5) + str.charCodeAt(i);
|
||||
return val;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5 || APP-PLUS-NVUE || MP-360
|
||||
var {
|
||||
windowWidth,
|
||||
platform
|
||||
} = uni.getSystemInfoSync(),
|
||||
cfg = require('./libs/config.js');
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
var weexDom = weex.requireModule('dom');
|
||||
// #endif
|
||||
/**
|
||||
* Parser 富文本组件
|
||||
* @tutorial https://github.com/jin-yufeng/Parser
|
||||
* @property {String} html 富文本数据
|
||||
* @property {Boolean} autopause 是否在播放一个视频时自动暂停其他视频
|
||||
* @property {Boolean} autoscroll 是否自动给所有表格添加一个滚动层
|
||||
* @property {Boolean} autosetTitle 是否自动将 title 标签中的内容设置到页面标题
|
||||
* @property {Number} compress 压缩等级
|
||||
* @property {String} domain 图片、视频等链接的主域名
|
||||
* @property {Boolean} lazyLoad 是否开启图片懒加载
|
||||
* @property {String} loadingImg 图片加载完成前的占位图
|
||||
* @property {Boolean} selectable 是否开启长按复制
|
||||
* @property {Object} tagStyle 标签的默认样式
|
||||
* @property {Boolean} showWithAnimation 是否使用渐显动画
|
||||
* @property {Boolean} useAnchor 是否使用锚点
|
||||
* @property {Boolean} useCache 是否缓存解析结果
|
||||
* @event {Function} parse 解析完成事件
|
||||
* @event {Function} load dom 加载完成事件
|
||||
* @event {Function} ready 所有图片加载完毕事件
|
||||
* @event {Function} error 错误事件
|
||||
* @event {Function} imgtap 图片点击事件
|
||||
* @event {Function} linkpress 链接点击事件
|
||||
* @author JinYufeng
|
||||
* @version 20201029
|
||||
* @listens MIT
|
||||
*/
|
||||
export default {
|
||||
name: 'parser',
|
||||
data() {
|
||||
return {
|
||||
// #ifdef H5 || MP-360
|
||||
uid: this._uid,
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
height: 1,
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
showAm: '',
|
||||
// #endif
|
||||
nodes: []
|
||||
}
|
||||
},
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
components: {
|
||||
trees
|
||||
},
|
||||
// #endif
|
||||
props: {
|
||||
html: String,
|
||||
autopause: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoscroll: Boolean,
|
||||
autosetTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
compress: Number,
|
||||
loadingImg: String,
|
||||
useCache: Boolean,
|
||||
// #endif
|
||||
domain: String,
|
||||
lazyLoad: Boolean,
|
||||
selectable: Boolean,
|
||||
tagStyle: Object,
|
||||
showWithAnimation: Boolean,
|
||||
useAnchor: Boolean
|
||||
},
|
||||
watch: {
|
||||
html(html) {
|
||||
this.setContent(html);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 图片数组
|
||||
this.imgList = [];
|
||||
this.imgList.each = function(f) {
|
||||
for (var i = 0, len = this.length; i < len; i++)
|
||||
this.setItem(i, f(this[i], i, this));
|
||||
}
|
||||
this.imgList.setItem = function(i, src) {
|
||||
if (i == void 0 || !src) return;
|
||||
// #ifndef MP-ALIPAY || APP-PLUS
|
||||
// 去重
|
||||
if (src.indexOf('http') == 0 && this.includes(src)) {
|
||||
var newSrc = src.split('://')[0];
|
||||
for (var j = newSrc.length, c; c = src[j]; j++) {
|
||||
if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break;
|
||||
newSrc += Math.random() > 0.5 ? c.toUpperCase() : c;
|
||||
}
|
||||
newSrc += src.substr(j);
|
||||
return this[i] = newSrc;
|
||||
}
|
||||
// #endif
|
||||
this[i] = src;
|
||||
// 暂存 data src
|
||||
if (src.includes('data:image')) {
|
||||
var filePath, info = src.match(/data:image\/(\S+?);(\S+?),(.+)/);
|
||||
if (!info) return;
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`;
|
||||
fs && fs.writeFile({
|
||||
filePath,
|
||||
data: info[3],
|
||||
encoding: info[2],
|
||||
success: () => this[i] = filePath
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
filePath = `_doc/parser_tmp/${Date.now()}.${info[1]}`;
|
||||
var bitmap = new plus.nativeObj.Bitmap();
|
||||
bitmap.loadBase64Data(src, () => {
|
||||
bitmap.save(filePath, {}, () => {
|
||||
bitmap.clear()
|
||||
this[i] = filePath;
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef H5 || MP-360
|
||||
this.document = document.getElementById('rtf' + this._uid);
|
||||
// #endif
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
if (dom) this.document = new dom(this);
|
||||
// #endif
|
||||
if (search) this.search = args => search(this, args);
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
this.document = this.$refs.web;
|
||||
setTimeout(() => {
|
||||
// #endif
|
||||
if (this.html) this.setContent(this.html);
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
}, 30)
|
||||
// #endif
|
||||
},
|
||||
beforeDestroy() {
|
||||
// #ifdef H5 || MP-360
|
||||
if (this._observer) this._observer.disconnect();
|
||||
// #endif
|
||||
this.imgList.each(src => {
|
||||
// #ifdef APP-PLUS
|
||||
if (src && src.includes('_doc')) {
|
||||
plus.io.resolveLocalFileSystemURL(src, entry => {
|
||||
entry.remove();
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
if (src && src.includes(uni.env.USER_DATA_PATH))
|
||||
fs && fs.unlink({
|
||||
filePath: src
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
clearInterval(this._timer);
|
||||
},
|
||||
methods: {
|
||||
// 设置富文本内容
|
||||
setContent(html, append) {
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (!html)
|
||||
return this.height = 1;
|
||||
if (append)
|
||||
this.$refs.web.evalJs("var b=document.createElement('div');b.innerHTML='" + html.replace(/'/g, "\\'") +
|
||||
"';document.getElementById('parser').appendChild(b)");
|
||||
else {
|
||||
html =
|
||||
'<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>html,body{width:100%;height:100%;overflow:hidden}body{margin:0}</style><base href="' +
|
||||
this.domain + '"><div id="parser"' + (this.selectable ? '>' : ' style="user-select:none">') + this._handleHtml(html).replace(/\n/g, '\\n') +
|
||||
'</div><script>"use strict";function e(e){if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){var t={data:[e]};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(t):window.__dcloud_weex_.postMessage(JSON.stringify(t))}}document.body.onclick=function(){e({action:"click"})},' +
|
||||
(this.showWithAnimation ? 'document.body.style.animation="_show .5s",' : '') +
|
||||
'setTimeout(function(){e({action:"load",text:document.body.innerText,height:document.getElementById("parser").scrollHeight})},50);\x3c/script>';
|
||||
if (platform == 'android') html = html.replace(/%/g, '%25');
|
||||
this.$refs.web.evalJs("document.write('" + html.replace(/'/g, "\\'") + "');document.close()");
|
||||
}
|
||||
this.$refs.web.evalJs(
|
||||
'var t=document.getElementsByTagName("title");t.length&&e({action:"getTitle",title:t[0].innerText});for(var o,n=document.getElementsByTagName("style"),r=1;o=n[r++];)o.innerHTML=o.innerHTML.replace(/body/g,"#parser");for(var a,c=document.getElementsByTagName("img"),s=[],i=0==c.length,d=0,l=0,g=0;a=c[l];l++)parseInt(a.style.width||a.getAttribute("width"))>' +
|
||||
windowWidth + '&&(a.style.height="auto"),a.onload=function(){++d==c.length&&(i=!0)},a.onerror=function(){++d==c.length&&(i=!0),' + (cfg.errorImg ? 'this.src="' + cfg.errorImg + '",' : '') +
|
||||
'e({action:"error",source:"img",target:this})},a.hasAttribute("ignore")||"A"==a.parentElement.nodeName||(a.i=g++,s.push(a.getAttribute("original-src")||a.src||a.getAttribute("data-src")),a.onclick=function(t){t.stopPropagation(),e({action:"preview",img:{i:this.i,src:this.src}})});e({action:"getImgList",imgList:s});for(var u,m=document.getElementsByTagName("a"),f=0;u=m[f];f++)u.onclick=function(m){m.stopPropagation();var t,o=this.getAttribute("href");if("#"==o[0]){var n=document.getElementById(o.substr(1));n&&(t=n.offsetTop)}return e({action:"linkpress",href:o,offset:t}),!1};for(var h,y=document.getElementsByTagName("video"),v=0;h=y[v];v++)h.style.maxWidth="100%",h.onerror=function(){e({action:"error",source:"video",target:this})}' +
|
||||
(this.autopause ? ',h.onplay=function(){for(var e,t=0;e=y[t];t++)e!=this&&e.pause()}' : '') +
|
||||
';for(var _,p=document.getElementsByTagName("audio"),w=0;_=p[w];w++)_.onerror=function(){e({action:"error",source:"audio",target:this})};' +
|
||||
(this.autoscroll ? 'for(var T,E=document.getElementsByTagName("table"),B=0;T=E[B];B++){var N=document.createElement("div");N.style.overflow="scroll",T.parentNode.replaceChild(N,T),N.appendChild(T)}' : '') +
|
||||
'var x=document.getElementById("parser");clearInterval(window.timer),window.timer=setInterval(function(){i&&clearInterval(window.timer),e({action:"ready",ready:i,height:x.scrollHeight})},350)'
|
||||
)
|
||||
this.nodes = [1];
|
||||
// #endif
|
||||
// #ifdef H5 || MP-360
|
||||
if (!html) {
|
||||
if (this.rtf && !append) this.rtf.parentNode.removeChild(this.rtf);
|
||||
return;
|
||||
}
|
||||
var div = document.createElement('div');
|
||||
if (!append) {
|
||||
if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
|
||||
this.rtf = div;
|
||||
} else {
|
||||
if (!this.rtf) this.rtf = div;
|
||||
else this.rtf.appendChild(div);
|
||||
}
|
||||
div.innerHTML = this._handleHtml(html, append);
|
||||
for (var styles = this.rtf.getElementsByTagName('style'), i = 0, style; style = styles[i++];) {
|
||||
style.innerHTML = style.innerHTML.replace(/body/g, '#rtf' + this._uid);
|
||||
style.setAttribute('scoped', 'true');
|
||||
}
|
||||
// 懒加载
|
||||
if (!this._observer && this.lazyLoad && IntersectionObserver) {
|
||||
this._observer = new IntersectionObserver(changes => {
|
||||
for (let item, i = 0; item = changes[i++];) {
|
||||
if (item.isIntersecting) {
|
||||
item.target.src = item.target.getAttribute('data-src');
|
||||
item.target.removeAttribute('data-src');
|
||||
this._observer.unobserve(item.target);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
rootMargin: '500px 0px 500px 0px'
|
||||
})
|
||||
}
|
||||
var _ts = this;
|
||||
// 获取标题
|
||||
var title = this.rtf.getElementsByTagName('title');
|
||||
if (title.length && this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: title[0].innerText
|
||||
})
|
||||
// 填充 domain
|
||||
var fill = target => {
|
||||
var src = target.getAttribute('src');
|
||||
if (this.domain && src) {
|
||||
if (src[0] == '/') {
|
||||
if (src[1] == '/')
|
||||
target.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
|
||||
else target.src = this.domain + src;
|
||||
} else if (!src.includes('://') && src.indexOf('data:') != 0) target.src = this.domain + '/' + src;
|
||||
}
|
||||
}
|
||||
// 图片处理
|
||||
this.imgList.length = 0;
|
||||
var imgs = this.rtf.getElementsByTagName('img');
|
||||
for (let i = 0, j = 0, img; img = imgs[i]; i++) {
|
||||
if (parseInt(img.style.width || img.getAttribute('width')) > windowWidth)
|
||||
img.style.height = 'auto';
|
||||
fill(img);
|
||||
if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
|
||||
img.i = j++;
|
||||
_ts.imgList.push(img.getAttribute('original-src') || img.src || img.getAttribute('data-src'));
|
||||
img.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
var preview = true;
|
||||
this.ignore = () => preview = false;
|
||||
_ts.$emit('imgtap', this);
|
||||
if (preview) {
|
||||
uni.previewImage({
|
||||
current: this.i,
|
||||
urls: _ts.imgList
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
img.onerror = function() {
|
||||
if (cfg.errorImg)
|
||||
_ts.imgList[this.i] = this.src = cfg.errorImg;
|
||||
_ts.$emit('error', {
|
||||
source: 'img',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
if (_ts.lazyLoad && this._observer && img.src && img.i != 0) {
|
||||
img.setAttribute('data-src', img.src);
|
||||
img.removeAttribute('src');
|
||||
this._observer.observe(img);
|
||||
}
|
||||
}
|
||||
// 链接处理
|
||||
var links = this.rtf.getElementsByTagName('a');
|
||||
for (var link of links) {
|
||||
link.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
var jump = true,
|
||||
href = this.getAttribute('href');
|
||||
_ts.$emit('linkpress', {
|
||||
href,
|
||||
ignore: () => jump = false
|
||||
});
|
||||
if (jump && href) {
|
||||
if (href[0] == '#') {
|
||||
if (_ts.useAnchor) {
|
||||
_ts.navigateTo({
|
||||
id: href.substr(1)
|
||||
})
|
||||
}
|
||||
} else if (href.indexOf('http') == 0 || href.indexOf('//') == 0)
|
||||
return true;
|
||||
else
|
||||
uni.navigateTo({
|
||||
url: href
|
||||
})
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 视频处理
|
||||
var videos = this.rtf.getElementsByTagName('video');
|
||||
_ts.videoContexts = videos;
|
||||
for (let video, i = 0; video = videos[i++];) {
|
||||
fill(video);
|
||||
video.style.maxWidth = '100%';
|
||||
video.onerror = function() {
|
||||
_ts.$emit('error', {
|
||||
source: 'video',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
video.onplay = function() {
|
||||
if (_ts.autopause)
|
||||
for (let item, i = 0; item = _ts.videoContexts[i++];)
|
||||
if (item != this) item.pause();
|
||||
}
|
||||
}
|
||||
// 音频处理
|
||||
var audios = this.rtf.getElementsByTagName('audio');
|
||||
for (var audio of audios) {
|
||||
fill(audio);
|
||||
audio.onerror = function() {
|
||||
_ts.$emit('error', {
|
||||
source: 'audio',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
}
|
||||
// 表格处理
|
||||
if (this.autoscroll) {
|
||||
var tables = this.rtf.getElementsByTagName('table');
|
||||
for (var table of tables) {
|
||||
let div = document.createElement('div');
|
||||
div.style.overflow = 'scroll';
|
||||
table.parentNode.replaceChild(div, table);
|
||||
div.appendChild(table);
|
||||
}
|
||||
}
|
||||
if (!append) this.document.appendChild(this.rtf);
|
||||
this.$nextTick(() => {
|
||||
this.nodes = [1];
|
||||
this.$emit('load');
|
||||
});
|
||||
setTimeout(() => this.showAm = '', 500);
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
// #ifndef H5 || MP-360
|
||||
var nodes;
|
||||
if (!html) return this.nodes = [];
|
||||
var parser = new Parser(html, this);
|
||||
// 缓存读取
|
||||
if (this.useCache) {
|
||||
var hashVal = hash(html);
|
||||
if (cache[hashVal])
|
||||
nodes = cache[hashVal];
|
||||
else {
|
||||
nodes = parser.parse();
|
||||
cache[hashVal] = nodes;
|
||||
}
|
||||
} else nodes = parser.parse();
|
||||
this.$emit('parse', nodes);
|
||||
if (append) this.nodes = this.nodes.concat(nodes);
|
||||
else this.nodes = nodes;
|
||||
if (nodes.length && nodes.title && this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: nodes.title
|
||||
})
|
||||
if (this.imgList) this.imgList.length = 0;
|
||||
this.videoContexts = [];
|
||||
this.$nextTick(() => {
|
||||
(function f(cs) {
|
||||
for (var i = cs.length; i--;) {
|
||||
if (cs[i].top) {
|
||||
cs[i].controls = [];
|
||||
cs[i].init();
|
||||
f(cs[i].$children);
|
||||
}
|
||||
}
|
||||
})(this.$children)
|
||||
this.$emit('load');
|
||||
})
|
||||
// #endif
|
||||
var height;
|
||||
clearInterval(this._timer);
|
||||
this._timer = setInterval(() => {
|
||||
// #ifdef H5 || MP-360
|
||||
this.rect = this.rtf.getBoundingClientRect();
|
||||
// #endif
|
||||
// #ifndef H5 || MP-360
|
||||
uni.createSelectorQuery().in(this)
|
||||
.select('#_top').boundingClientRect().exec(res => {
|
||||
if (!res) return;
|
||||
this.rect = res[0];
|
||||
// #endif
|
||||
if (this.rect.height == height) {
|
||||
this.$emit('ready', this.rect)
|
||||
clearInterval(this._timer);
|
||||
}
|
||||
height = this.rect.height;
|
||||
// #ifndef H5 || MP-360
|
||||
});
|
||||
// #endif
|
||||
}, 350);
|
||||
if (this.showWithAnimation && !append) this.showAm = 'animation:_show .5s';
|
||||
// #endif
|
||||
},
|
||||
// 获取文本内容
|
||||
getText(ns = this.nodes) {
|
||||
var txt = '';
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
txt = this._text;
|
||||
// #endif
|
||||
// #ifdef H5 || MP-360
|
||||
txt = this.rtf.innerText;
|
||||
// #endif
|
||||
// #ifndef H5 || APP-PLUS-NVUE || MP-360
|
||||
for (var i = 0, n; n = ns[i++];) {
|
||||
if (n.type == 'text') txt += n.text.replace(/ /g, '\u00A0').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/&/g, '&');
|
||||
else if (n.type == 'br') txt += '\n';
|
||||
else {
|
||||
// 块级标签前后加换行
|
||||
var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
|
||||
'0' && n.name[1] < '7');
|
||||
if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
|
||||
if (n.children) txt += this.getText(n.children);
|
||||
if (block && txt[txt.length - 1] != '\n') txt += '\n';
|
||||
else if (n.name == 'td' || n.name == 'th') txt += '\t';
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
return txt;
|
||||
},
|
||||
// 锚点跳转
|
||||
in (obj) {
|
||||
if (obj.page && obj.selector && obj.scrollTop) this._in = obj;
|
||||
},
|
||||
navigateTo(obj) {
|
||||
if (!this.useAnchor) return obj.fail && obj.fail('Anchor is disabled');
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (!obj.id)
|
||||
weexDom.scrollToElement(this.$refs.web);
|
||||
else
|
||||
this.$refs.web.evalJs('var pos=document.getElementById("' + obj.id +
|
||||
'");if(pos)post({action:"linkpress",href:"#",offset:pos.offsetTop+' + (obj.offset || 0) + '})');
|
||||
obj.success && obj.success();
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
var d = ' ';
|
||||
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
|
||||
d = '>>>';
|
||||
// #endif
|
||||
var selector = uni.createSelectorQuery().in(this._in ? this._in.page : this).select((this._in ? this._in.selector :
|
||||
'#_top') + (obj.id ? `${d}#${obj.id},${this._in?this._in.selector:'#_top'}${d}.${obj.id}` : '')).boundingClientRect();
|
||||
if (this._in) selector.select(this._in.selector).scrollOffset().select(this._in.selector).boundingClientRect();
|
||||
else selector.selectViewport().scrollOffset();
|
||||
selector.exec(res => {
|
||||
if (!res[0]) return obj.fail && obj.fail('Label not found')
|
||||
var scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + (obj.offset || 0);
|
||||
if (this._in) this._in.page[this._in.scrollTop] = scrollTop;
|
||||
else uni.pageScrollTo({
|
||||
scrollTop,
|
||||
duration: 300
|
||||
})
|
||||
obj.success && obj.success();
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
// 获取视频对象
|
||||
getVideoContext(id) {
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
if (!id) return this.videoContexts;
|
||||
else
|
||||
for (var i = this.videoContexts.length; i--;)
|
||||
if (this.videoContexts[i].id == id) return this.videoContexts[i];
|
||||
// #endif
|
||||
},
|
||||
// #ifdef H5 || APP-PLUS-NVUE || MP-360
|
||||
_handleHtml(html, append) {
|
||||
if (!append) {
|
||||
// 处理 tag-style 和 userAgentStyles
|
||||
var style = '<style>@keyframes _show{0%{opacity:0}100%{opacity:1}}img{max-width:100%}';
|
||||
for (var item in cfg.userAgentStyles)
|
||||
style += `${item}{${cfg.userAgentStyles[item]}}`;
|
||||
for (item in this.tagStyle)
|
||||
style += `${item}{${this.tagStyle[item]}}`;
|
||||
style += '</style>';
|
||||
html = style + html;
|
||||
}
|
||||
// 处理 rpx
|
||||
if (html.includes('rpx'))
|
||||
html = html.replace(/[0-9.]+\s*rpx/g, $ => (parseFloat($) * windowWidth / 750) + 'px');
|
||||
return html;
|
||||
},
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
_message(e) {
|
||||
// 接收 web-view 消息
|
||||
var d = e.detail.data[0];
|
||||
switch (d.action) {
|
||||
case 'load':
|
||||
this.$emit('load');
|
||||
this.height = d.height;
|
||||
this._text = d.text;
|
||||
break;
|
||||
case 'getTitle':
|
||||
if (this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: d.title
|
||||
})
|
||||
break;
|
||||
case 'getImgList':
|
||||
this.imgList.length = 0;
|
||||
for (var i = d.imgList.length; i--;)
|
||||
this.imgList.setItem(i, d.imgList[i]);
|
||||
break;
|
||||
case 'preview':
|
||||
var preview = true;
|
||||
d.img.ignore = () => preview = false;
|
||||
this.$emit('imgtap', d.img);
|
||||
if (preview)
|
||||
uni.previewImage({
|
||||
current: d.img.i,
|
||||
urls: this.imgList
|
||||
})
|
||||
break;
|
||||
case 'linkpress':
|
||||
var jump = true,
|
||||
href = d.href;
|
||||
this.$emit('linkpress', {
|
||||
href,
|
||||
ignore: () => jump = false
|
||||
})
|
||||
if (jump && href) {
|
||||
if (href[0] == '#') {
|
||||
if (this.useAnchor)
|
||||
weexDom.scrollToElement(this.$refs.web, {
|
||||
offset: d.offset
|
||||
})
|
||||
} else if (href.includes('://'))
|
||||
plus.runtime.openWeb(href);
|
||||
else
|
||||
uni.navigateTo({
|
||||
url: href
|
||||
})
|
||||
}
|
||||
break;
|
||||
case 'error':
|
||||
if (d.source == 'img' && cfg.errorImg)
|
||||
this.imgList.setItem(d.target.i, cfg.errorImg);
|
||||
this.$emit('error', {
|
||||
source: d.source,
|
||||
target: d.target
|
||||
})
|
||||
break;
|
||||
case 'ready':
|
||||
this.height = d.height;
|
||||
if (d.ready) uni.createSelectorQuery().in(this).select('#_top').boundingClientRect().exec(res => {
|
||||
this.rect = res[0];
|
||||
this.$emit('ready', res[0]);
|
||||
})
|
||||
break;
|
||||
case 'click':
|
||||
this.$emit('click');
|
||||
this.$emit('tap');
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes _show {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
:host {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
|
@ -0,0 +1,676 @@
|
|||
<template>
|
||||
<u-popup :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex">
|
||||
<view class="u-datetime-picker">
|
||||
<view class="u-picker-header" @touchmove.stop.prevent="">
|
||||
<view class="u-btn-picker u-btn-picker--tips"
|
||||
:style="{ color: cancelColor }"
|
||||
hover-class="u-opacity"
|
||||
:hover-stay-time="150"
|
||||
@tap="getResult('cancel')"
|
||||
>{{cancelText}}</view>
|
||||
<view class="u-picker__title">{{ title }}</view>
|
||||
<view
|
||||
class="u-btn-picker u-btn-picker--primary"
|
||||
:style="{ color: moving ? cancelColor : confirmColor }"
|
||||
hover-class="u-opacity"
|
||||
:hover-stay-time="150"
|
||||
@touchmove.stop=""
|
||||
@tap.stop="getResult('confirm')"
|
||||
>
|
||||
{{confirmText}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-picker-body">
|
||||
<picker-view v-if="mode == 'region'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
|
||||
<picker-view-column v-if="!reset && params.province">
|
||||
<view class="u-column-item" v-for="(item, index) in provinces" :key="index">
|
||||
<view class="u-line-1">{{ item.label }}</view>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="!reset && params.city">
|
||||
<view class="u-column-item" v-for="(item, index) in citys" :key="index">
|
||||
<view class="u-line-1">{{ item.label }}</view>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="!reset && params.area">
|
||||
<view class="u-column-item" v-for="(item, index) in areas" :key="index">
|
||||
<view class="u-line-1">{{ item.label }}</view>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
<picker-view v-else-if="mode == 'time'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
|
||||
<picker-view-column v-if="!reset && params.year">
|
||||
<view class="u-column-item" v-for="(item, index) in years" :key="index">
|
||||
{{ item }}
|
||||
<text class="u-text" v-if="showTimeTag">年</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="!reset && params.month">
|
||||
<view class="u-column-item" v-for="(item, index) in months" :key="index">
|
||||
{{ formatNumber(item) }}
|
||||
<text class="u-text" v-if="showTimeTag">月</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="!reset && params.day">
|
||||
<view class="u-column-item" v-for="(item, index) in days" :key="index">
|
||||
{{ formatNumber(item) }}
|
||||
<text class="u-text" v-if="showTimeTag">日</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="!reset && params.hour">
|
||||
<view class="u-column-item" v-for="(item, index) in hours" :key="index">
|
||||
{{ formatNumber(item) }}
|
||||
<text class="u-text" v-if="showTimeTag">时</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="!reset && params.minute">
|
||||
<view class="u-column-item" v-for="(item, index) in minutes" :key="index">
|
||||
{{ formatNumber(item) }}
|
||||
<text class="u-text" v-if="showTimeTag">分</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column v-if="!reset && params.second">
|
||||
<view class="u-column-item" v-for="(item, index) in seconds" :key="index">
|
||||
{{ formatNumber(item) }}
|
||||
<text class="u-text" v-if="showTimeTag">秒</text>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
<picker-view v-else-if="mode == 'selector'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
|
||||
<picker-view-column v-if="!reset">
|
||||
<view class="u-column-item" v-for="(item, index) in range" :key="index">
|
||||
<view class="u-line-1">{{ getItemValue(item, 'selector') }}</view>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
<picker-view v-else-if="mode == 'multiSelector'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
|
||||
<picker-view-column v-if="!reset" v-for="(item, index) in range" :key="index">
|
||||
<view class="u-column-item" v-for="(item1, index1) in item" :key="index1">
|
||||
<view class="u-line-1">{{ getItemValue(item1, 'multiSelector') }}</view>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import provinces from '../../libs/util/province.js';
|
||||
import citys from '../../libs/util/city.js';
|
||||
import areas from '../../libs/util/area.js';
|
||||
|
||||
/**
|
||||
* picker picker弹出选择器
|
||||
* @description 此选择器有两种弹出模式:一是时间模式,可以配置年,日,月,时,分,秒参数 二是地区模式,可以配置省,市,区参数
|
||||
* @tutorial https://www.uviewui.com/components/picker.html
|
||||
* @property {Object} params 需要显示的参数,见官网说明
|
||||
* @property {String} mode 模式选择,region-地区类型,time-时间类型(默认time)
|
||||
* @property {String Number} start-year 可选的开始年份,mode=time时有效(默认1950)
|
||||
* @property {String Number} end-year 可选的结束年份,mode=time时有效(默认2050)
|
||||
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
|
||||
* @property {Boolean} show-time-tag 时间模式时,是否显示后面的年月日中文提示
|
||||
* @property {String} cancel-color 取消按钮的颜色(默认#606266)
|
||||
* @property {String} confirm-color 确认按钮的颜色(默认#2979ff)
|
||||
* @property {String} default-time 默认选中的时间,mode=time时有效
|
||||
* @property {String} confirm-text 确认按钮的文字
|
||||
* @property {String} cancel-text 取消按钮的文字
|
||||
* @property {String} default-region 默认选中的地区,中文形式,mode=region时有效
|
||||
* @property {String} default-code 默认选中的地区,编号形式,mode=region时有效
|
||||
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
|
||||
* @property {String Number} z-index 弹出时的z-index值(默认1075)
|
||||
* @property {Array} default-selector 数组形式,其中每一项表示选择了range对应项中的第几个
|
||||
* @property {Array} range 自定义选择的数据,mode=selector或mode=multiSelector时有效
|
||||
* @property {String} range-key 当range参数的元素为对象时,指定Object中的哪个key的值作为选择器显示内容
|
||||
* @event {Function} confirm 点击确定按钮,返回当前选择的值
|
||||
* @event {Function} cancel 点击取消按钮,返回当前选择的值
|
||||
* @example <u-picker v-model="show" mode="time"></u-picker>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-picker',
|
||||
props: {
|
||||
// picker中需要显示的参数
|
||||
params: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
year: true,
|
||||
month: true,
|
||||
day: true,
|
||||
hour: false,
|
||||
minute: false,
|
||||
second: false,
|
||||
province: true,
|
||||
city: true,
|
||||
area: true,
|
||||
timestamp: true,
|
||||
};
|
||||
}
|
||||
},
|
||||
// 当mode=selector或者mode=multiSelector时,提供的数组
|
||||
range: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 当mode=selector或者mode=multiSelector时,提供的默认选中的下标
|
||||
defaultSelector: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [0];
|
||||
}
|
||||
},
|
||||
// 当 range 是一个 Array<Object> 时,通过 range-key 来指定 Object 中 key 的值作为选择器显示内容
|
||||
rangeKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 模式选择,region-地区类型,time-时间类型,selector-单列模式,multiSelector-多列模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'time'
|
||||
},
|
||||
// 年份开始时间
|
||||
startYear: {
|
||||
type: [String, Number],
|
||||
default: 1950
|
||||
},
|
||||
// 年份结束时间
|
||||
endYear: {
|
||||
type: [String, Number],
|
||||
default: 2050
|
||||
},
|
||||
// "取消"按钮的颜色
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// "确定"按钮的颜色
|
||||
confirmColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 默认显示的时间,2025-07-02 || 2025-07-02 13:01:00 || 2025/07/02
|
||||
defaultTime: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 默认显示的地区,可传类似["河北省", "秦皇岛市", "北戴河区"]
|
||||
defaultRegion: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 时间模式时,是否显示后面的年月日中文提示
|
||||
showTimeTag: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 默认显示地区的编码,defaultRegion和areaCode同时存在,areaCode优先,可传类似["13", "1303", "130304"]
|
||||
areaCode: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许通过点击遮罩关闭Picker
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 通过双向绑定控制组件的弹出与收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹出的z-index值
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 顶部标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 取消按钮的文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 确认按钮的文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
years: [],
|
||||
months: [],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
year: 0,
|
||||
month: 0,
|
||||
day: 0,
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
reset: false,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
valueArr: [],
|
||||
provinces: provinces,
|
||||
citys: citys[0],
|
||||
areas: areas[0][0],
|
||||
province: 0,
|
||||
city: 0,
|
||||
area: 0,
|
||||
moving: false // 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
computed: {
|
||||
propsChange() {
|
||||
// 引用这几个变量,是为了监听其变化
|
||||
return `${this.mode}-${this.defaultTime}-${this.startYear}-${this.endYear}-${this.defaultRegion}-${this.areaCode}`;
|
||||
},
|
||||
regionChange() {
|
||||
// 引用这几个变量,是为了监听其变化
|
||||
return `${this.province}-${this.city}`;
|
||||
},
|
||||
yearAndMonth() {
|
||||
return `${this.year}-${this.month}`;
|
||||
},
|
||||
uZIndex() {
|
||||
// 如果用户有传递z-index值,优先使用
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
propsChange() {
|
||||
this.reset = true;
|
||||
setTimeout(() => this.init(), 10);
|
||||
},
|
||||
// 如果地区发生变化,为了让picker联动起来,必须重置this.citys和this.areas
|
||||
regionChange(val) {
|
||||
this.citys = citys[this.province];
|
||||
this.areas = areas[this.province][this.city];
|
||||
},
|
||||
// watch监听月份的变化,实时变更日的天数,因为不同月份,天数不一样
|
||||
// 一个月可能有30,31天,甚至闰年2月的29天,平年2月28天
|
||||
yearAndMonth(val) {
|
||||
if (this.params.year) this.setDays();
|
||||
},
|
||||
// 微信和QQ小程序由于一些奇怪的原因(故同时对所有平台均初始化一遍),需要重新初始化才能显示正确的值
|
||||
value(n) {
|
||||
if (n) {
|
||||
this.reset = true;
|
||||
setTimeout(() => this.init(), 10);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 标识滑动开始,只有微信小程序才有这样的事件
|
||||
pickstart() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.moving = true;
|
||||
// #endif
|
||||
},
|
||||
// 标识滑动结束
|
||||
pickend() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.moving = false;
|
||||
// #endif
|
||||
},
|
||||
// 对单列和多列形式的判断是否有传入变量的情况
|
||||
getItemValue(item, mode) {
|
||||
// 目前(2020-05-25)uni-app对微信小程序编译有错误,导致v-if为false中的内容也执行,错误导致
|
||||
// 单列模式或者多列模式中的getItemValue同时被执行,故在这里再加一层判断
|
||||
if (this.mode == mode) {
|
||||
return typeof item == 'object' ? item[this.rangeKey] : item;
|
||||
}
|
||||
},
|
||||
// 小于10前面补0,用于月份,日期,时分秒等
|
||||
formatNumber(num) {
|
||||
return +num < 10 ? '0' + num : String(num);
|
||||
},
|
||||
// 生成递进的数组
|
||||
generateArray: function(start, end) {
|
||||
// 转为数值格式,否则用户给end-year等传递字符串值时,下面的end+1会导致字符串拼接,而不是相加
|
||||
start = Number(start);
|
||||
end = Number(end);
|
||||
end = end > start ? end : start;
|
||||
// 生成数组,获取其中的索引,并剪出来
|
||||
return [...Array(end + 1).keys()].slice(start);
|
||||
},
|
||||
getIndex: function(arr, val) {
|
||||
let index = arr.indexOf(val);
|
||||
// 如果index为-1(即找不到index值),~(-1)=-(-1)-1=0,导致条件不成立
|
||||
return ~index ? index : 0;
|
||||
},
|
||||
//日期时间处理
|
||||
initTimeValue() {
|
||||
// 格式化时间,在IE浏览器(uni不存在此情况),无法识别日期间的"-"间隔符号
|
||||
let fdate = this.defaultTime.replace(/\-/g, '/');
|
||||
fdate = fdate && fdate.indexOf('/') == -1 ? `2020/01/01 ${fdate}` : fdate;
|
||||
let time = null;
|
||||
if (fdate) time = new Date(fdate);
|
||||
else time = new Date();
|
||||
// 获取年日月时分秒
|
||||
this.year = time.getFullYear();
|
||||
this.month = Number(time.getMonth()) + 1;
|
||||
this.day = time.getDate();
|
||||
this.hour = time.getHours();
|
||||
this.minute = time.getMinutes();
|
||||
this.second = time.getSeconds();
|
||||
},
|
||||
init() {
|
||||
this.valueArr = [];
|
||||
this.reset = false;
|
||||
if (this.mode == 'time') {
|
||||
this.initTimeValue();
|
||||
if (this.params.year) {
|
||||
this.valueArr.push(0);
|
||||
this.setYears();
|
||||
}
|
||||
if (this.params.month) {
|
||||
this.valueArr.push(0);
|
||||
this.setMonths();
|
||||
}
|
||||
if (this.params.day) {
|
||||
this.valueArr.push(0);
|
||||
this.setDays();
|
||||
}
|
||||
if (this.params.hour) {
|
||||
this.valueArr.push(0);
|
||||
this.setHours();
|
||||
}
|
||||
if (this.params.minute) {
|
||||
this.valueArr.push(0);
|
||||
this.setMinutes();
|
||||
}
|
||||
if (this.params.second) {
|
||||
this.valueArr.push(0);
|
||||
this.setSeconds();
|
||||
}
|
||||
} else if (this.mode == 'region') {
|
||||
if (this.params.province) {
|
||||
this.valueArr.push(0);
|
||||
this.setProvinces();
|
||||
}
|
||||
if (this.params.city) {
|
||||
this.valueArr.push(0);
|
||||
this.setCitys();
|
||||
}
|
||||
if (this.params.area) {
|
||||
this.valueArr.push(0);
|
||||
this.setAreas();
|
||||
}
|
||||
} else if (this.mode == 'selector') {
|
||||
this.valueArr = this.defaultSelector;
|
||||
} else if (this.mode == 'multiSelector') {
|
||||
this.valueArr = this.defaultSelector;
|
||||
this.multiSelectorValue = this.defaultSelector;
|
||||
}
|
||||
this.$forceUpdate();
|
||||
},
|
||||
// 设置picker的某一列值
|
||||
setYears() {
|
||||
// 获取年份集合
|
||||
this.years = this.generateArray(this.startYear, this.endYear);
|
||||
// 设置this.valueArr某一项的值,是为了让picker预选中某一个值
|
||||
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.years, this.year));
|
||||
},
|
||||
setMonths() {
|
||||
this.months = this.generateArray(1, 12);
|
||||
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.months, this.month));
|
||||
},
|
||||
setDays() {
|
||||
let totalDays = new Date(this.year, this.month, 0).getDate();
|
||||
this.days = this.generateArray(1, totalDays);
|
||||
let index = 0;
|
||||
// 这里不能使用类似setMonths()中的this.valueArr.splice(this.valueArr.length - 1, xxx)做法
|
||||
// 因为this.month和this.year变化时,会触发watch中的this.setDays(),导致this.valueArr.length计算有误
|
||||
if (this.params.year && this.params.month) index = 2;
|
||||
else if (this.params.month) index = 1;
|
||||
else if (this.params.year) index = 1;
|
||||
else index = 0;
|
||||
// 当月份变化时,会导致日期的天数也会变化,如果原来选的天数大于变化后的天数,则重置为变化后的最大值
|
||||
// 比如原来选中3月31日,调整为2月后,日期变为最大29,这时如果day值继续为31显然不合理,于是将其置为29(picker-column从1开始)
|
||||
if(this.day > this.days.length) this.day = this.days.length;
|
||||
this.valueArr.splice(index, 1, this.getIndex(this.days, this.day));
|
||||
},
|
||||
setHours() {
|
||||
this.hours = this.generateArray(0, 23);
|
||||
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.hours, this.hour));
|
||||
},
|
||||
setMinutes() {
|
||||
this.minutes = this.generateArray(0, 59);
|
||||
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.minutes, this.minute));
|
||||
},
|
||||
setSeconds() {
|
||||
this.seconds = this.generateArray(0, 59);
|
||||
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.seconds, this.second));
|
||||
},
|
||||
setProvinces() {
|
||||
// 判断是否需要province参数
|
||||
if (!this.params.province) return;
|
||||
let tmp = '';
|
||||
let useCode = false;
|
||||
// 如果同时配置了defaultRegion和areaCode,优先使用areaCode参数
|
||||
if (this.areaCode.length) {
|
||||
tmp = this.areaCode[0];
|
||||
useCode = true;
|
||||
} else if (this.defaultRegion.length) tmp = this.defaultRegion[0];
|
||||
else tmp = 0;
|
||||
// 历遍省份数组匹配
|
||||
provinces.map((v, k) => {
|
||||
if (useCode ? v.value == tmp : v.label == tmp) {
|
||||
tmp = k;
|
||||
}
|
||||
});
|
||||
this.province = tmp;
|
||||
this.provinces = provinces;
|
||||
// 设置默认省份的值
|
||||
this.valueArr.splice(0, 1, this.province);
|
||||
},
|
||||
setCitys() {
|
||||
if (!this.params.city) return;
|
||||
let tmp = '';
|
||||
let useCode = false;
|
||||
if (this.areaCode.length) {
|
||||
tmp = this.areaCode[1];
|
||||
useCode = true;
|
||||
} else if (this.defaultRegion.length) tmp = this.defaultRegion[1];
|
||||
else tmp = 0;
|
||||
citys[this.province].map((v, k) => {
|
||||
if (useCode ? v.value == tmp : v.label == tmp) {
|
||||
tmp = k;
|
||||
}
|
||||
});
|
||||
this.city = tmp;
|
||||
this.citys = citys[this.province];
|
||||
this.valueArr.splice(1, 1, this.city);
|
||||
},
|
||||
setAreas() {
|
||||
if (!this.params.area) return;
|
||||
let tmp = '';
|
||||
let useCode = false;
|
||||
if (this.areaCode.length) {
|
||||
tmp = this.areaCode[2];
|
||||
useCode = true;
|
||||
} else if (this.defaultRegion.length) tmp = this.defaultRegion[2];
|
||||
else tmp = 0;
|
||||
areas[this.province][this.city].map((v, k) => {
|
||||
if (useCode ? v.value == tmp : v.label == tmp) {
|
||||
tmp = k;
|
||||
}
|
||||
});
|
||||
this.area = tmp;
|
||||
this.areas = areas[this.province][this.city];
|
||||
this.valueArr.splice(2, 1, this.area);
|
||||
},
|
||||
close() {
|
||||
this.$emit('input', false);
|
||||
},
|
||||
// 用户更改picker的列选项
|
||||
change(e) {
|
||||
this.valueArr = e.detail.value;
|
||||
let i = 0;
|
||||
if (this.mode == 'time') {
|
||||
// 这里使用i++,是因为this.valueArr数组的长度是不确定长度的,它根据this.params的值来配置长度
|
||||
// 进入if规则,i会加1,保证了能获取准确的值
|
||||
if (this.params.year) this.year = this.years[this.valueArr[i++]];
|
||||
if (this.params.month) this.month = this.months[this.valueArr[i++]];
|
||||
if (this.params.day) this.day = this.days[this.valueArr[i++]];
|
||||
if (this.params.hour) this.hour = this.hours[this.valueArr[i++]];
|
||||
if (this.params.minute) this.minute = this.minutes[this.valueArr[i++]];
|
||||
if (this.params.second) this.second = this.seconds[this.valueArr[i++]];
|
||||
} else if (this.mode == 'region') {
|
||||
if (this.params.province) this.province = this.valueArr[i++];
|
||||
if (this.params.city) this.city = this.valueArr[i++];
|
||||
if (this.params.area) this.area = this.valueArr[i++];
|
||||
} else if (this.mode == 'multiSelector') {
|
||||
let index = null;
|
||||
// 对比前后两个数组,寻找变更的是哪一列,如果某一个元素不同,即可判定该列发生了变化
|
||||
this.defaultSelector.map((val, idx) => {
|
||||
if (val != e.detail.value[idx]) index = idx;
|
||||
});
|
||||
// 为了让用户对多列变化时,对动态设置其他列的变更
|
||||
if (index != null) {
|
||||
this.$emit('columnchange', {
|
||||
column: index,
|
||||
index: e.detail.value[index]
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
// 用户点击确定按钮
|
||||
getResult(event = null) {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.moving) return;
|
||||
// #endif
|
||||
let result = {};
|
||||
// 只返回用户在this.params中配置了为true的字段
|
||||
if (this.mode == 'time') {
|
||||
if (this.params.year) result.year = this.formatNumber(this.year || 0);
|
||||
if (this.params.month) result.month = this.formatNumber(this.month || 0);
|
||||
if (this.params.day) result.day = this.formatNumber(this.day || 0);
|
||||
if (this.params.hour) result.hour = this.formatNumber(this.hour || 0);
|
||||
if (this.params.minute) result.minute = this.formatNumber(this.minute || 0);
|
||||
if (this.params.second) result.second = this.formatNumber(this.second || 0);
|
||||
if (this.params.timestamp) result.timestamp = this.getTimestamp();
|
||||
} else if (this.mode == 'region') {
|
||||
if (this.params.province) result.province = provinces[this.province];
|
||||
if (this.params.city) result.city = citys[this.province][this.city];
|
||||
if (this.params.area) result.area = areas[this.province][this.city][this.area];
|
||||
} else if (this.mode == 'selector') {
|
||||
result = this.valueArr;
|
||||
} else if (this.mode == 'multiSelector') {
|
||||
result = this.valueArr;
|
||||
}
|
||||
if (event) this.$emit(event, result);
|
||||
this.close();
|
||||
},
|
||||
// 获取时间戳
|
||||
getTimestamp() {
|
||||
// yyyy-mm-dd为安卓写法,不支持iOS,需要使用"/"分隔,才能二者兼容
|
||||
let time = this.year + '/' + this.month + '/' + this.day + ' ' + this.hour + ':' + this.minute + ':' + this.second;
|
||||
return new Date(time).getTime() / 1000;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../libs/css/style.components.scss';
|
||||
|
||||
.u-datetime-picker {
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.u-picker-view {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.u-picker-header {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
padding: 0 40rpx;
|
||||
@include vue-flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
font-size: 30rpx;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.u-picker-header::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 1rpx solid #eaeef1;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.u-picker__title {
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
.u-picker-body {
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.u-column-item {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: $u-main-color;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
|
||||
.u-text {
|
||||
font-size: 24rpx;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
.u-btn-picker {
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.u-opacity {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.u-btn-picker--primary {
|
||||
color: $u-type-primary;
|
||||
}
|
||||
|
||||
.u-btn-picker--tips {
|
||||
color: $u-tips-color;
|
||||
}
|
||||
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue