-
25.gitignore
-
21App.vue
-
128README.md
-
256components/nx-search/nx-search.vue
-
33main.js
-
85manifest.json
-
30nxTemp/apis/userAPI.js
-
30nxTemp/config/index.config.js
-
278nxTemp/config/requestConfig.js
-
11nxTemp/filter/index.js
-
26nxTemp/index.js
-
129nxTemp/request/core/request.js
-
84nxTemp/request/core/utils.js
-
7nxTemp/request/index.js
-
313nxTemp/request/request.md
-
169nxTemp/request/upload/qiniuUploader.js
-
214nxTemp/request/upload/upload.js
-
234nxTemp/request/upload/utils.js
-
73nxTemp/router/index.js
-
1nxTemp/router/uni-simple-router.js
-
19nxTemp/store/index.js
-
67nxTemp/store/modules/user.js
-
165nxTemp/utils/tools.js
-
35nxTemp/wechat/wechat.js
-
16package-lock.json
-
6package.json
-
152pages.json
-
327pages/event/event_grade.vue
-
196pages/event/event_list.vue
-
118pages/index/index.vue
-
27pages/login/agreement.vue
-
255pages/login/forget.vue
-
242pages/login/login.vue
-
303pages/login/reg.vue
-
30pages/me/index.vue
-
74pages/message/center_list.vue
-
69pages/message/detail.vue
-
13pages/public/404.vue
-
184static/colorui/animation.css
-
69static/colorui/components/cu-custom.vue
-
1226static/colorui/icon.css
-
3912static/colorui/main.css
-
BINstatic/images/event/enent_none.png
-
BINstatic/images/event/event_close.png
-
BINstatic/images/event/event_location.png
-
BINstatic/images/event/event_lock.png
-
BINstatic/images/index/arrow_right.png
-
BINstatic/images/index/index_bell.png
-
BINstatic/images/index/index_bg.png
-
BINstatic/images/index/index_cup.png
-
BINstatic/images/index/index_logout.png
-
BINstatic/images/login/agree_circle.png
-
BINstatic/images/login/agreed_circle.png
-
BINstatic/images/login/ty0.png
-
BINstatic/images/login/ty1.png
-
BINstatic/images/logo_main.png
-
BINstatic/images/tabbar/tab_home_01.png
-
BINstatic/images/tabbar/tab_home_02.png
-
BINstatic/images/tabbar/tab_user_01.png
-
BINstatic/images/tabbar/tab_user_02.png
-
131static/style/base.scss
-
164static/style/mixin/flex.scss
-
26static/style/mixin/hr.scss
-
16static/style/mixin/position-absolute.scss
-
5static/style/mixin/price-before.scss
-
16static/style/mixin/text-overflow.scss
-
16static/style/mixin/triangle.scss
-
33template.h5.html
-
76uni.scss
-
21uview-ui/LICENSE
-
105uview-ui/README.md
-
35uview-ui/changelog.md
-
74uview-ui/components/u--form/u--form.vue
-
40uview-ui/components/u--image/u--image.vue
-
63uview-ui/components/u--input/u--input.vue
-
46uview-ui/components/u--text/u--text.vue
-
47uview-ui/components/u--textarea/u--textarea.vue
-
54uview-ui/components/u-action-sheet/props.js
-
275uview-ui/components/u-action-sheet/u-action-sheet.vue
-
59uview-ui/components/u-album/props.js
-
236uview-ui/components/u-album/u-album.vue
-
44uview-ui/components/u-alert/props.js
-
243uview-ui/components/u-alert/u-alert.vue
-
46uview-ui/components/u-avatar-group/props.js
-
103uview-ui/components/u-avatar-group/u-avatar-group.vue
-
78uview-ui/components/u-avatar/props.js
-
163uview-ui/components/u-avatar/u-avatar.vue
-
54uview-ui/components/u-back-top/props.js
-
137uview-ui/components/u-back-top/u-back-top.vue
-
72uview-ui/components/u-badge/props.js
-
171uview-ui/components/u-badge/u-badge.vue
-
46uview-ui/components/u-button/nvue.scss
-
156uview-ui/components/u-button/props.js
-
485uview-ui/components/u-button/u-button.vue
-
73uview-ui/components/u-button/vue.scss
-
99uview-ui/components/u-calendar/header.vue
-
570uview-ui/components/u-calendar/month.vue
-
134uview-ui/components/u-calendar/props.js
-
288uview-ui/components/u-calendar/u-calendar.vue
-
85uview-ui/components/u-calendar/util.js
@ -0,0 +1,25 @@ |
|||
.DS_Store |
|||
node_modules/ |
|||
unpackage/ |
|||
dist/ |
|||
pageTemp.vue |
|||
.hbuilderx/ |
|||
|
|||
# local env files |
|||
.env.local |
|||
.env.*.local |
|||
|
|||
# Log files |
|||
npm-debug.log* |
|||
yarn-debug.log* |
|||
yarn-error.log* |
|||
|
|||
# Editor directories and files |
|||
.project |
|||
.idea |
|||
.vscode |
|||
*.suo |
|||
*.ntvs* |
|||
*.njsproj |
|||
*.sln |
|||
*.sw* |
@ -0,0 +1,21 @@ |
|||
<script> |
|||
import { |
|||
init |
|||
} from "@/nxTemp"; |
|||
export default { |
|||
onLaunch(options) { |
|||
console.log('App onLaunch') |
|||
init(options); |
|||
}, |
|||
onShow: function() { |
|||
console.log('App Show') |
|||
}, |
|||
onHide: function() { |
|||
console.log('App Hide') |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "static/style/base.scss"; |
|||
</style> |
@ -0,0 +1,128 @@ |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
## 1、全局路由守卫 |
|||
|
|||
##### (1) 路由拦截 |
|||
[uni-simple-router](https://hhyang.cn/) 路由、拦截、最优雅的解决方案 |
|||
|
|||
##### (2) 路由配置 |
|||
通过 vue.config.js 配合[uni-read-pages](https://github.com/SilurianYang/uni-read-pages),可以随心所欲的读取 pages.json 下的所有配置 |
|||
|
|||
|
|||
## 2、Request封装 |
|||
适用于一项目多域名请求、七牛云图片上传、本地服务器图片上传、支持 Promise. |
|||
|
|||
## 3、api集中管理 |
|||
api集中管理; 为简化逻辑代码量整洁的原则,像调用函数一样调用api,做到代码分离,在apis目录统一创建api函数 |
|||
|
|||
## 4、小程序更新提示代码,配置分包,等必备代码 |
|||
sub目录分包管理 由于微信小程序的限制,上传发布机制总包大小不能大于2m,所以项目若超出该限制,要在page.json中做分包处理,分包处理的配置与pages目录保持一致,封装更新提示代码 |
|||
|
|||
## 5、配置vuex |
|||
不需要引入每个子store模块 |
|||
|
|||
```javascript |
|||
import Vue from "vue"; |
|||
import Vuex from "vuex"; |
|||
|
|||
Vue.use(Vuex); |
|||
const files = require.context("./modules", false, /\.js$/); |
|||
let modules = { |
|||
state: {}, |
|||
mutations: {}, |
|||
actions: {} |
|||
}; |
|||
|
|||
files.keys().forEach((key) => { |
|||
Object.assign(modules.state, files(key)["state"]); |
|||
Object.assign(modules.mutations, files(key)["mutations"]); |
|||
Object.assign(modules.actions, files(key)["actions"]); |
|||
}); |
|||
const store = new Vuex.Store(modules); |
|||
export default store; |
|||
``` |
|||
页面使用Vuex |
|||
|
|||
```javascript |
|||
import { mapState,mapActions } from 'vuex'; |
|||
|
|||
computed: { |
|||
...mapState(['userInfo']) |
|||
} |
|||
methods: { |
|||
...mapActions(['getUserInfo']) |
|||
} |
|||
|
|||
``` |
|||
|
|||
通用的mutations方法,只需要写一个就行了 |
|||
|
|||
```javascript |
|||
//更新state数据 |
|||
setStateAttr(state, param) { |
|||
if (param instanceof Array) { |
|||
for (let item of param) { |
|||
state[item.key] = item.val; |
|||
} |
|||
} else { |
|||
state[param.key] = param.val; |
|||
} |
|||
} |
|||
``` |
|||
actions调用 |
|||
|
|||
```javascript |
|||
async setUserData({ |
|||
state, |
|||
commit |
|||
}, data) { |
|||
commit('setStateAttr', { |
|||
key: 'userInfo', |
|||
val: data |
|||
}) |
|||
uni.setStorageSync('userInfo', data); |
|||
}, |
|||
``` |
|||
|
|||
## 6、全局过滤器filters |
|||
main.js引入filters,使用如下 |
|||
```javascript |
|||
{{shop.shopAddress|autoAddPoints}} |
|||
``` |
|||
|
|||
## 7、无关系组件间的通信=>事件车 |
|||
> 事件车的基本原理就是在本项目Vue的原型对象里新生成一个Vue对象专门用来负责无关系,跨级组件间的通信 |
|||
|
|||
main.js声明事件bus |
|||
```javascript |
|||
Vue.prototype.$bus = new Vue() // event Bus 用于无关系组件间的通信。 |
|||
``` |
|||
A组件 监听($on) |
|||
|
|||
```javascript |
|||
// onload 里面 |
|||
this.$bus.$on('updateChecked', this.updateChecked) |
|||
|
|||
// methods 里面 |
|||
updateChecked(index){ |
|||
console.log('这里就拿到了跨级组件的index',index) |
|||
} |
|||
``` |
|||
B组件 触发($emit) |
|||
> B组件触发A组件的updateChecked 传index值给A组件 |
|||
```javascript |
|||
this.$bus.$emit('updateChecked', index); |
|||
``` |
|||
|
|||
## [github源码下载](https://github.com/mgbq/uni-template) |
|||
|
|||
## [插件市场源码](https://ext.dcloud.net.cn/plugin?id=4008) |
|||
|
|||
## 常见问题 |
|||
#### 1 运行不了,控制台报错,请安装依赖 |
|||
|
|||
```npm install ``` |
|||
|
@ -0,0 +1,256 @@ |
|||
<template> |
|||
<view class="serach"> |
|||
<view class="content" :style="{ 'border-radius': radius + 'px' }"> |
|||
<view class="content-box"> |
|||
<!-- 下拉选择框 --> |
|||
<view class="seach-select" v-if="selectList.length>0"> |
|||
<!-- 选中值 --> |
|||
<view class="select-value" @click="selectClick"> |
|||
{{selectList[selectIndex].name}} |
|||
<text class="cuIcon-triangledownfill" style=""></text> |
|||
</view> |
|||
<!-- 选择列表 --> |
|||
<view class="select-item-list" :style="{'display':(showSelectList)?'block':'none'}"> |
|||
<text class="cuIcon-triangleupfill" |
|||
style="position: absolute;top: -18px;left: 60rpx;font-size: 30rpx;color: #FFFFFF;"></text> |
|||
<view :class="['select-item',(index>0)?'item-border':'']" v-for="(data,index) in selectList" |
|||
:key="index" @click="selectItem(index)">{{data.name}}</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<text class="cuIcon-search" v-if="showSeachIcon" style="margin: 0 10rpx;"></text> |
|||
<input :placeholder="placeholder" @input="inputChange" confirm-type="search" @confirm="triggerConfirm" |
|||
class="input" :focus="isFocus" v-model="inputVal" @focus="focus" @blur="blur" /> |
|||
<text v-if="isDelShow" class="cuIcon-roundclose" @tap.stop="clear"></text> |
|||
</view> |
|||
<view v-if=" |
|||
(showSeachBth && button === 'inside') || |
|||
(isDelShow && button === 'inside') |
|||
" class="serachBtn" style="background-color: #C0191F;color: #fff;" @tap="search"> |
|||
搜索 |
|||
</view> |
|||
</view> |
|||
<view v-if="button === 'outside'" class="button" :class="{ active: showSeachBth }" @tap="search"> |
|||
<view class="button-item">{{ !showSeachBth ? searchName : '搜索' }}</view> |
|||
</view> |
|||
<view v-show="showSelectList" @click="selectClick" class="page-mask"></view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
selectList: { |
|||
type: Array, |
|||
default: [ |
|||
// { |
|||
// id: 1, |
|||
// name: '产品' |
|||
// }, |
|||
// { |
|||
// id: 2, |
|||
// name: '内容' |
|||
// } |
|||
|
|||
] |
|||
}, |
|||
placeholder: { |
|||
value: String, |
|||
default: '请输入搜索内容' |
|||
}, |
|||
value: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
button: { |
|||
value: String, |
|||
default: 'outside' |
|||
}, |
|||
showSeachIcon: { |
|||
value: Boolean, |
|||
default: true |
|||
}, |
|||
showSeachBth: { |
|||
value: Boolean, |
|||
default: true |
|||
}, |
|||
radius: { |
|||
value: String, |
|||
default: 60 |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
showSelectList: false, |
|||
selectIndex: 0, |
|||
isFocus: true, |
|||
inputVal: '', |
|||
searchName: '取消', |
|||
isDelShow: false |
|||
}; |
|||
}, |
|||
methods: { |
|||
selectItem(index) { |
|||
this.selectIndex = index |
|||
this.showSelectList = !this.showSelectList; |
|||
}, |
|||
selectClick() { |
|||
this.showSelectList = !this.showSelectList; |
|||
}, |
|||
triggerConfirm() { |
|||
|
|||
let searchQuery = { |
|||
keyword: this.inputVal |
|||
} |
|||
if (this.selectList.length > 0) { |
|||
searchQuery.selectIndex = this.selectIndex; |
|||
} |
|||
this.$emit('confirm', searchQuery); |
|||
}, |
|||
inputChange(event) { |
|||
const keyword = event.detail.value; |
|||
this.$emit('input', keyword); |
|||
if (this.inputVal) { |
|||
this.isDelShow = true; |
|||
} |
|||
}, |
|||
focus() { |
|||
if (this.inputVal) { |
|||
this.isDelShow = true; |
|||
} |
|||
}, |
|||
blur() { |
|||
this.isFocus = false; |
|||
uni.hideKeyboard(); |
|||
}, |
|||
clear() { |
|||
uni.hideKeyboard(); |
|||
this.isFocus = false; |
|||
this.inputVal = ''; |
|||
this.$emit('input', ''); |
|||
}, |
|||
getFocus() { |
|||
this.isFocus = true; |
|||
}, |
|||
search() { |
|||
let searchQuery = { |
|||
keyword: this.inputVal |
|||
} |
|||
if (this.selectList.length > 0) { |
|||
searchQuery.selectIndex = this.selectIndex; |
|||
} |
|||
this.$emit('search', searchQuery); |
|||
} |
|||
}, |
|||
watch: { |
|||
inputVal(newVal) { |
|||
if (newVal) { |
|||
this.searchName = '搜索'; |
|||
} else { |
|||
this.searchName = '取消'; |
|||
this.isDelShow = false; |
|||
} |
|||
}, |
|||
value(val) { |
|||
this.inputVal = val.trim(); |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.serach { |
|||
display: flex; |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
font-size: $uni-font-size-base; |
|||
|
|||
.content { |
|||
display: flex; |
|||
align-items: center; |
|||
width: 100%; |
|||
height: 60upx; |
|||
background: #fff; |
|||
transition: all 0.2s linear; |
|||
border-radius: 30px; |
|||
|
|||
.content-box { |
|||
width: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
|
|||
.seach-select { |
|||
min-width: 100rpx; |
|||
margin-left: 10px; |
|||
position: relative; |
|||
max-width: 100rpx; |
|||
|
|||
.select-item-list { |
|||
background-color: #FFFFFF; |
|||
position: absolute; |
|||
top: 75rpx; |
|||
width: 150rpx; |
|||
left: -20rpx; |
|||
border-radius: 10rpx; |
|||
z-index: 10; |
|||
box-shadow: 0px 0px 5px #C9C9C9; |
|||
|
|||
.select-item { |
|||
text-align: center; |
|||
padding: 10rpx 0; |
|||
} |
|||
|
|||
.item-border { |
|||
border-top: 1rpx solid #EEEEEE; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.input { |
|||
width: 100%; |
|||
max-width: 100%; |
|||
line-height: 60upx; |
|||
height: 60upx; |
|||
transition: all 0.2s linear; |
|||
|
|||
} |
|||
} |
|||
|
|||
.serachBtn { |
|||
height: 100%; |
|||
flex-shrink: 0; |
|||
padding: 0 30upx; |
|||
line-height: 60upx; |
|||
transition: all 0.3s; |
|||
border-top-right-radius: 60px; |
|||
border-bottom-right-radius: 60px; |
|||
} |
|||
} |
|||
|
|||
.button { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
position: relative; |
|||
flex-shrink: 0; |
|||
width: 0; |
|||
transition: all 0.2s linear; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
|
|||
&.active { |
|||
padding-left: 15upx; |
|||
width: 100upx; |
|||
} |
|||
} |
|||
|
|||
.page-mask { |
|||
position: fixed; |
|||
top: 0; |
|||
bottom: 0; |
|||
right: 0; |
|||
left: 0; |
|||
z-index: 5; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,33 @@ |
|||
import Vue from "vue"; |
|||
import App from "./App"; |
|||
import { |
|||
router, |
|||
RouterMount |
|||
} from "@/nxTemp/router"; |
|||
import store from "@/nxTemp/store"; |
|||
import uView from "@/uview-ui"; |
|||
import nxTemp from "@/nxTemp"; |
|||
|
|||
|
|||
async function bootstrap() { |
|||
App.mpType = "app"; |
|||
//引入路由
|
|||
Vue.use(router); |
|||
// 引入全局uView
|
|||
Vue.use(uView); |
|||
// 加载nxTemp
|
|||
Vue.use(nxTemp); |
|||
|
|||
const app = new Vue({ |
|||
store, |
|||
...App |
|||
}); |
|||
// #ifdef H5
|
|||
RouterMount(app, router, "#app"); |
|||
// #endif
|
|||
// #ifndef H5
|
|||
app.$mount(); |
|||
// #endif
|
|||
} |
|||
|
|||
bootstrap(); |
@ -0,0 +1,85 @@ |
|||
{ |
|||
"name" : "events-helper", |
|||
"appid" : "", |
|||
"description" : "", |
|||
"versionName" : "1.0.0", |
|||
"versionCode" : "100", |
|||
"transformPx" : false, |
|||
"app-plus" : { |
|||
"usingComponents" : true, |
|||
"nvueCompiler" : "uni-app", |
|||
"compilerVersion" : 3, |
|||
"splashscreen" : { |
|||
"alwaysShowBeforeRender" : true, |
|||
"waiting" : true, |
|||
"autoclose" : true, |
|||
"delay" : 0 |
|||
}, |
|||
"modules" : {}, |
|||
"distribute" : { |
|||
"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.READ_CONTACTS\"/>", |
|||
"<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.WRITE_CONTACTS\"/>", |
|||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", |
|||
"<uses-permission android:name=\"android.permission.CAMERA\"/>", |
|||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>", |
|||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", |
|||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>", |
|||
"<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.CALL_PHONE\"/>", |
|||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", |
|||
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>", |
|||
"<uses-feature android:name=\"android.hardware.camera\"/>", |
|||
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>", |
|||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" |
|||
] |
|||
}, |
|||
"ios" : {}, |
|||
"sdkConfigs" : {} |
|||
} |
|||
}, |
|||
"quickapp" : {}, |
|||
"mp-weixin" : { |
|||
"appid" : "wxacdbba29503f6351", |
|||
"setting" : { |
|||
"urlCheck" : false, |
|||
"minified" : false, |
|||
"es6" : true |
|||
}, |
|||
"optimization" : { |
|||
"subPackages" : true |
|||
}, |
|||
"usingComponents" : true |
|||
}, |
|||
"mp-alipay" : { |
|||
"usingComponents" : true |
|||
}, |
|||
"mp-baidu" : { |
|||
"usingComponents" : true |
|||
}, |
|||
"mp-toutiao" : { |
|||
"usingComponents" : true |
|||
}, |
|||
"uniStatistics" : { |
|||
"enable" : false |
|||
}, |
|||
"h5" : { |
|||
"template" : "template.h5.html", |
|||
"router" : { |
|||
"mode" : "history", |
|||
"base" : "" |
|||
}, |
|||
"devServer" : { |
|||
"https" : false |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
import http from '@/nxTemp/config/requestConfig' |
|||
import config from "@/nxTemp/config/index.config.js"; |
|||
|
|||
// 用户登录
|
|||
export function postLogin(data) { |
|||
return http.post(`${config.baseUrl}/gaMatchAssistant/login`, data ,{ |
|||
needLogin:false |
|||
}); |
|||
} |
|||
|
|||
//发送验证码
|
|||
export function postSendSMS(data) { |
|||
return http.post(`${config.baseUrl}/gaMatchAssistant/captchaSms/send`, data); |
|||
} |
|||
|
|||
/** |
|||
* @description: 小程序授权手机号码 |
|||
* @param |
|||
code: loginRes.code, |
|||
appid: this.APPID, |
|||
encryptedData: e.detail.encryptedData, |
|||
iv: e.detail.iv, |
|||
* @return: {*} |
|||
* @author: |
|||
*/ |
|||
export function wechatGetPhoneNumber(data) { |
|||
return http.post(`${config.baseUrl}/user/wechatGetPhoneNumber`, data); |
|||
} |
|||
|
|||
|
@ -0,0 +1,30 @@ |
|||
const CONFIG = { |
|||
//开发环境配置
|
|||
development: { |
|||
loginTitleTxt: "development_wx", // 登录页标题
|
|||
copyrightTxt: "XXXv1.0", // 版本信息
|
|||
assetsPath: "http://cdn.com/img", // 静态资源路径
|
|||
baseUrl: "http://testmanager.ouxuanzhineng.cn", |
|||
tokenKey: "wxf368fa7316d6952f", // 登录标识
|
|||
testOpenId: "", // 小程序测试openId
|
|||
forcedLogin: false, // touristMode游客模式下APP是否强制用户登录 场景:当用户进入登录页面后无法后退。
|
|||
touristMode: true, // APP是否开启游客模式, 游客模式true开启:APP打开后可以进入首页和无权限的页面,游客模式false关闭:APP打开后首先需要登录才能进入, 此时forcedLogin配置项失效。
|
|||
showLog:true, //是否开启log
|
|||
}, |
|||
|
|||
//生产环境配置
|
|||
production: { |
|||
loginTitleTxt: "production_wx", // 登录页标题
|
|||
copyrightTxt: "XXXv1.0", // 版本信息
|
|||
assetsPath: "/static/img", // 静态资源路径
|
|||
baseUrl: "http://testmanager.ouxuanzhineng.cn", |
|||
tokenKey: "WECHAT_TRADE", // 登录标识
|
|||
testOpenId: "wxf368fa7316d6952f", // 小程序测试openId
|
|||
forcedLogin: false, // touristMode游客模式下APP是否强制用户登录 场景:当用户进入登录页面后无法后退。
|
|||
touristMode: true, // APP是否开启游客模式, 游客模式true开启:APP打开后可以进入首页和无权限的页面,游客模式false关闭:APP打开后首先需要登录才能进入, 此时forcedLogin配置项失效。
|
|||
showLog:true, //是否开启log
|
|||
} |
|||
|
|||
} |
|||
|
|||
export default CONFIG[process.env.NODE_ENV]; |
@ -0,0 +1,278 @@ |
|||
import request from "@/nxTemp/request"; |
|||
import store from '@/nxTemp/store'; |
|||
import config from '@/nxTemp/config/index.config.js'; |
|||
|
|||
//可以new多个request来支持多个域名请求
|
|||
let $http = new request({ |
|||
//接口请求地址
|
|||
baseUrl: config.baseUrl, |
|||
//服务器本地上传文件地址
|
|||
fileUrl: config.baseUrl, |
|||
// 服务器上传图片默认url
|
|||
defaultUploadUrl: "api/common/v1/upload_image", |
|||
//设置请求头(如果使用报错跨域问题,可能是content-type请求类型和后台那边设置的不一致)
|
|||
header: { |
|||
'Content-Type': 'application/json;charset=UTF-8', |
|||
// 'project_token': config.projectToken, //项目token(可删除)
|
|||
} |
|||
}); |
|||
// 添加获取七牛云token的方法
|
|||
$http.getQnToken = function(callback) { |
|||
//该地址需要开发者自行配置(每个后台的接口风格都不一样)
|
|||
$http.get("api/common/v1/qn_upload").then(data => { |
|||
/* |
|||
*接口返回参数: |
|||
*visitPrefix:访问文件的域名 |
|||
*token:七牛云上传token |
|||
*folderPath:上传的文件夹 |
|||
*region: 地区 默认为:SCN |
|||
*/ |
|||
callback({ |
|||
visitPrefix: data.visitPrefix, |
|||
token: data.token, |
|||
folderPath: data.folderPath |
|||
}); |
|||
}); |
|||
} |
|||
//请求开始拦截器
|
|||
$http.requestStart = function(options) { |
|||
// console.log("请求开始", options);
|
|||
if (options.load) { |
|||
//打开加载动画
|
|||
store.commit("setLoadingShow", true); |
|||
} |
|||
// 图片上传大小限制
|
|||
if (options.method == "FILE" && options.maxSize) { |
|||
// 文件最大字节: options.maxSize 可以在调用方法的时候加入参数
|
|||
let maxSize = options.maxSize; |
|||
for (let item of options.files) { |
|||
if (item.size > maxSize) { |
|||
setTimeout(() => { |
|||
uni.showToast({ |
|||
title: "图片过大,请重新上传", |
|||
icon: "none" |
|||
}); |
|||
}, 500); |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
//请求前加入token & token验证
|
|||
if (options.url) { |
|||
if (options.needLogin) { |
|||
let token = uni.getStorageSync('token'); |
|||
if (!token) { |
|||
store.dispatch('reLogin', '');//无token时,触发重新登录
|
|||
console.error(options.url,"缺少token,触发重新登录",options); |
|||
} else { |
|||
options.header['token'] = token; //header中带上token
|
|||
options.data['token'] = token; //请求data中带上token
|
|||
} |
|||
} |
|||
} |
|||
// if (options.url) {
|
|||
// //请求前加入token
|
|||
// let url = options.url.substring(options.url.lastIndexOf('/') + 1);
|
|||
// if (url != 'login') {
|
|||
// let token = uni.getStorageSync('token');
|
|||
// if (!token) {
|
|||
// console.log(url,"缺少token",options);
|
|||
// store.dispatch('reLogin', '');
|
|||
// } else {
|
|||
// options.header['token'] = uni.getStorageSync('token');
|
|||
// }
|
|||
// }
|
|||
// }
|
|||
|
|||
|
|||
return options; |
|||
} |
|||
//请求结束
|
|||
$http.requestEnd = function(options) { |
|||
//判断当前接口是否需要加载动画
|
|||
if (options.load) { |
|||
// 关闭加载动画
|
|||
store.commit("setLoadingShow", false); |
|||
} |
|||
} |
|||
//所有接口数据处理(此方法需要开发者根据各自的接口返回类型修改,以下只是模板)
|
|||
$http.dataFactory = async function(res) { |
|||
|
|||
//显示调试信息
|
|||
if(config.showLog){ |
|||
// console.log("接口请求数据", {
|
|||
// url: res.url,
|
|||
// resolve: res.response,
|
|||
// header: res.header,
|
|||
// data: res.data,
|
|||
// method: res.method,
|
|||
// });
|
|||
console.log("requestConfig:",res); |
|||
showLog(res.data,res.url,res.response) |
|||
} |
|||
|
|||
//验证需登录接口状态并给与提示
|
|||
if(res.needLogin&&!res.data.token){ |
|||
return Promise.reject({ |
|||
statusCode: 0, |
|||
errMsg: "requestConfig.js:该接口需登录后调用,请检查userAPI配置及逻辑", |
|||
data: res.url |
|||
}); |
|||
} |
|||
|
|||
if (res.response.statusCode && res.response.statusCode == 200) { |
|||
let httpData = res.response.data; |
|||
if (typeof(httpData) == "string") { |
|||
httpData = JSON.parse(httpData); |
|||
} |
|||
/*********以下只是模板(及共参考),需要开发者根据各自的接口返回类型修改*********/ |
|||
|
|||
//判断数据是否请求成功
|
|||
// if (httpData.success || httpData.code == 200) {
|
|||
if (httpData.code == 0) { |
|||
// 返回正确的结果(then接受数据)
|
|||
return Promise.resolve(httpData.data); |
|||
} else if (httpData.code == "1000" || httpData.code == "1001" || httpData.code == 1100 || httpData.code == 402) { |
|||
|
|||
// 失败重新请求(最多重新请求3次)
|
|||
// if(res.resend < 3){
|
|||
// let result = await $http.request({
|
|||
// url: res.url,
|
|||
// data: res.data,
|
|||
// method: res.method,
|
|||
// header: res.header,
|
|||
// isPrompt: res.isPrompt,//(默认 true 说明:本接口抛出的错误是否提示)
|
|||
// load: res.load,//(默认 true 说明:本接口是否提示加载动画)
|
|||
// isFactory: res.isFactory, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数将失去作用)
|
|||
// resend: res.resend += 1 // 当前重发次数
|
|||
// });
|
|||
// // 返回正确的结果(then接受数据)
|
|||
// return Promise.resolve(result);
|
|||
// }
|
|||
// 返回错误的结果(catch接受数据)
|
|||
// return Promise.reject({
|
|||
// statusCode: 0,
|
|||
// errMsg: "【request】" + (httpData.info || httpData.msg)
|
|||
// });
|
|||
|
|||
//----------------------------------------分割线---------------------------------------------------
|
|||
|
|||
// 刷新token在重新请求(最多重新请求2次)
|
|||
// if(res.resend < 2){
|
|||
// let tokenResult = await $http.request({
|
|||
// url: "http://localhost:7001/api/common/v1/protocol", // 获取token接口地址
|
|||
// data: {
|
|||
// type: 1000
|
|||
// }, // 获取接口参数
|
|||
// method: "GET",
|
|||
// load: false,//(默认 true 说明:本接口是否提示加载动画)
|
|||
// });
|
|||
// // 储存token
|
|||
// store.commit("userInfo", tokenResult);
|
|||
// let result = await $http.request({
|
|||
// url: res.url,
|
|||
// data: res.data,
|
|||
// method: res.method,
|
|||
// header: res.header,
|
|||
// isPrompt: res.isPrompt,//(默认 true 说明:本接口抛出的错误是否提示)
|
|||
// load: res.load,//(默认 true 说明:本接口是否提示加载动画)
|
|||
// isFactory: res.isFactory, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数将失去作用)
|
|||
// resend: res.resend += 1 // 当前重发次数
|
|||
// });
|
|||
// // 返回正确的结果(then接受数据)
|
|||
// return Promise.resolve(result);
|
|||
// }
|
|||
// 返回错误的结果(catch接受数据)
|
|||
// return Promise.reject({
|
|||
// statusCode: 0,
|
|||
// errMsg: "【request】" + (httpData.info || httpData.msg)
|
|||
// });
|
|||
|
|||
|
|||
store.commit("emptyUserInfo"); |
|||
// #ifdef MP-WEIXIN
|
|||
// onLogin();跳转登录页面
|
|||
// #endif
|
|||
|
|||
|
|||
// 返回错误的结果(catch接受数据)
|
|||
return Promise.reject({ |
|||
statusCode: 0, |
|||
errMsg: "【request】" + (httpData.info || httpData.msg), |
|||
data: res.data |
|||
}); |
|||
} else if (httpData.code == "401") { // token失效
|
|||
|
|||
uni.showToast({ |
|||
title: "登录状态失效请重新登录", |
|||
icon: "none" |
|||
}); |
|||
|
|||
|
|||
// 返回错误的结果(catch接受数据)
|
|||
// return Promise.reject({
|
|||
// statusCode: 0,
|
|||
// errMsg: "【request】" + (httpData.info || httpData.msg),
|
|||
// data: res.data
|
|||
// });
|
|||
} else { //其他错误提示
|
|||
if (res.isPrompt) { |
|||
uni.showToast({ |
|||
title: httpData.info || httpData.msg|| httpData.message, |
|||
icon: "none", |
|||
duration: 3000 |
|||
}); |
|||
} |
|||
// 返回错误的结果(catch接受数据)
|
|||
return Promise.reject({ |
|||
statusCode: 0, |
|||
errMsg: "【request】" + (httpData.info || httpData.msg|| httpData.message), |
|||
data: res.data |
|||
}); |
|||
} |
|||
|
|||
/*********以上只是模板(及共参考),需要开发者根据各自的接口返回类型修改*********/ |
|||
|
|||
} else { |
|||
// 返回错误的结果(catch接受数据)
|
|||
return Promise.reject({ |
|||
statusCode: res.response.statusCode, |
|||
errMsg: "【request】数据工厂验证不通过", |
|||
data: res.data |
|||
}); |
|||
} |
|||
}; |
|||
// 错误回调
|
|||
$http.requestError = function(e) { |
|||
// e.statusCode === 0 是参数效验错误抛出的
|
|||
console.log("-requestError-"); |
|||
if (e.statusCode === 0) { |
|||
throw e; |
|||
} else { |
|||
console.log(e); |
|||
uni.showToast({ |
|||
title: "网络错误,请检查一下网络", |
|||
icon: "none" |
|||
}); |
|||
} |
|||
} |
|||
function showLog(data,url,response){ |
|||
let weburl = getWebURL(data,url) |
|||
let temp = url.split("?")[0].split("/") |
|||
let postName = temp[temp.length-1] |
|||
console.warn("-------------------->> ["+postName+"][log]\n" |
|||
+"请求 Data: \n" + JSON.stringify(data) |
|||
+"\n URL:\n"+weburl |
|||
+"\n 服务端返回:\n"+JSON.stringify(response.data) |
|||
+"\n <<-------------------- ["+postName+"][log] ↑↑↑\n") |
|||
} |
|||
|
|||
function getWebURL(data,url){ |
|||
let result = "" |
|||
for(var i in data){ |
|||
result+=`&${i}=${data[i]}` |
|||
} |
|||
return url+"?"+result.slice(1) |
|||
} |
|||
export default $http; |
@ -0,0 +1,11 @@ |
|||
|
|||
// 自动添加省略号
|
|||
export function autoAddPoints(address) { |
|||
if (address && address.length > 17) { |
|||
return '...'.padStart(17, address); |
|||
} else { |
|||
return address; |
|||
} |
|||
|
|||
} |
|||
|
@ -0,0 +1,26 @@ |
|||
import store from '@/nxTemp/store'; |
|||
import wechat from '@/nxTemp/wechat/wechat'; |
|||
import tools from '@/nxTemp/utils/tools' |
|||
import * as filter from '@/nxTemp/filter'; |
|||
const install = Vue => { |
|||
// global filter
|
|||
Object.keys(filter).forEach(item => { |
|||
Vue.filter(item, filter[item]); |
|||
}); |
|||
// 挂载函数
|
|||
Vue.prototype.$store = store; |
|||
Vue.prototype.$tools = tools; |
|||
// event Bus 用于无关系组件间的通信。
|
|||
Vue.prototype.$bus = new Vue() |
|||
} |
|||
|
|||
export async function init(options) { |
|||
// #ifdef MP-WEIXIN
|
|||
// 检测小程序更新(如果从朋友圈场景进入则无此API)
|
|||
options.scene !== 1154 && wechat.checkMiniProgramUpdate(); |
|||
// #endif
|
|||
} |
|||
|
|||
export default { |
|||
install |
|||
} |
@ -0,0 +1,129 @@ |
|||
import { mergeConfig, dispatchRequest, jsonpRequest} from "./utils.js"; |
|||
export default class request { |
|||
constructor(options) { |
|||
//请求公共地址
|
|||
this.baseUrl = options.baseUrl || ""; |
|||
//公共文件上传请求地址
|
|||
this.fileUrl = options.fileUrl || ""; |
|||
// 服务器上传图片默认url
|
|||
this.defaultUploadUrl = options.defaultUploadUrl || ""; |
|||
//默认请求头
|
|||
this.header = options.header || {}; |
|||
//默认配置
|
|||
this.config = options.config || { |
|||
isPrompt: true, |
|||
load: false, |
|||
isFactory: true, |
|||
resend: 0, |
|||
needLogin:true,//默认所有接口需要登录验证,不需登录验证的如login接口需传参false声明
|
|||
}; |
|||
} |
|||
//post请求
|
|||
post(url = '', data = {}, options = {}) { |
|||
return this.request({ |
|||
method: "POST", |
|||
data: data, |
|||
url: url, |
|||
...options |
|||
}); |
|||
} |
|||
|
|||
//get请求
|
|||
get(url = '', data = {}, options = {}) { |
|||
return this.request({ |
|||
method: "GET", |
|||
data: data, |
|||
url: url, |
|||
...options |
|||
}); |
|||
} |
|||
|
|||
//put请求
|
|||
put(url = '', data = {}, options = {}) { |
|||
return this.request({ |
|||
method: "PUT", |
|||
data: data, |
|||
url: url, |
|||
...options |
|||
}); |
|||
} |
|||
|
|||
//delete请求
|
|||
delete(url = '', data = {}, options = {}) { |
|||
return this.request({ |
|||
method: "DELETE", |
|||
data: data, |
|||
url: url, |
|||
...options |
|||
}); |
|||
} |
|||
//jsonp请求(只限于H5使用)
|
|||
jsonp(url = '', data = {}, options = {}) { |
|||
return this.request({ |
|||
method: "JSONP", |
|||
data: data, |
|||
url: url, |
|||
...options |
|||
}); |
|||
} |
|||
//接口请求方法
|
|||
async request(data) { |
|||
// 请求数据
|
|||
let requestInfo, |
|||
// 是否运行过请求开始钩子
|
|||
runRequestStart = false; |
|||
try { |
|||
if (!data.url) { |
|||
throw { errMsg: "【request】缺失数据url", statusCode: 0} |
|||
} |
|||
// 数据合并
|
|||
requestInfo = mergeConfig(this, data); |
|||
// 代表之前运行到这里
|
|||
runRequestStart = true; |
|||
//请求前回调
|
|||
if (this.requestStart) { |
|||
let requestStart = this.requestStart(requestInfo); |
|||
if (typeof requestStart == "object") { |
|||
let changekeys = ["data", "header", "isPrompt", "load", "isFactory"]; |
|||
changekeys.forEach(key => { |
|||
requestInfo[key] = requestStart[key]; |
|||
}); |
|||
} else { |
|||
throw { |
|||
errMsg: "【request】请求开始拦截器未通过", |
|||
statusCode: 0, |
|||
data: requestInfo.data, |
|||
method: requestInfo.method, |
|||
header: requestInfo.header, |
|||
url: requestInfo.url, |
|||
} |
|||
} |
|||
} |
|||
let requestResult = {}; |
|||
if(requestInfo.method == "JSONP"){ |
|||
requestResult = await jsonpRequest(requestInfo); |
|||
} else { |
|||
requestResult = await dispatchRequest(requestInfo); |
|||
} |
|||
//是否用外部的数据处理方法
|
|||
if (requestInfo.isFactory && this.dataFactory) { |
|||
//数据处理
|
|||
let result = await this.dataFactory({ |
|||
...requestInfo, |
|||
response: requestResult |
|||
}); |
|||
return Promise.resolve(result); |
|||
} else { |
|||
return Promise.resolve(requestResult); |
|||
} |
|||
} catch (err){ |
|||
this.requestError && this.requestError(err); |
|||
return Promise.reject(err); |
|||
} finally { |
|||
// 如果请求开始未运行到,请求结束也不运行
|
|||
if(runRequestStart){ |
|||
this.requestEnd && this.requestEnd(requestInfo); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,84 @@ |
|||
// 获取合并的数据
|
|||
export const mergeConfig = function(_this, options) { |
|||
//判断url是不是链接
|
|||
let urlType = /^(http|https):\/\//.test(options.url); |
|||
let config = Object.assign({}, _this.config, options); |
|||
if (options.method == "FILE") { |
|||
config.url = urlType ? options.url : _this.fileUrl + options.url; |
|||
} else { |
|||
config.url = urlType ? options.url : _this.baseUrl + options.url; |
|||
} |
|||
//请求头
|
|||
if (options.header) { |
|||
config.header = Object.assign({}, _this.header, options.header); |
|||
} else { |
|||
config.header = _this.header; |
|||
} |
|||
return config; |
|||
} |
|||
// 请求
|
|||
export const dispatchRequest = function(requestInfo) { |
|||
return new Promise((resolve, reject) => { |
|||
let requestData = { |
|||
url: requestInfo.url, |
|||
header: requestInfo.header, //加入请求头
|
|||
success: (res) => { |
|||
resolve(res); |
|||
}, |
|||
fail: (err) => { |
|||
reject(err); |
|||
} |
|||
}; |
|||
//请求类型
|
|||
if (requestInfo.method) { |
|||
requestData.method = requestInfo.method; |
|||
} |
|||
if (requestInfo.data) { |
|||
requestData.data = requestInfo.data; |
|||
} |
|||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
|||
if (requestInfo.timeout) { |
|||
requestData.timeout = requestInfo.timeout; |
|||
} |
|||
// #endif
|
|||
if (requestInfo.dataType) { |
|||
requestData.dataType = requestInfo.dataType; |
|||
} |
|||
// #ifndef APP-PLUS || MP-ALIPAY
|
|||
if (requestInfo.responseType) { |
|||
requestData.responseType = requestInfo.responseType; |
|||
} |
|||
// #endif
|
|||
// #ifdef H5
|
|||
if (requestInfo.withCredentials) { |
|||
requestData.withCredentials = requestInfo.withCredentials; |
|||
} |
|||
// #endif
|
|||
uni.request(requestData); |
|||
}) |
|||
} |
|||
// jsonp请求
|
|||
export const jsonpRequest = function(requestInfo) { |
|||
return new Promise((resolve, reject) => { |
|||
let dataStr = ''; |
|||
Object.keys(requestInfo.data).forEach(key => { |
|||
dataStr += key + '=' + requestInfo.data[key] + '&'; |
|||
}); |
|||
//匹配最后一个&并去除
|
|||
if (dataStr !== '') { |
|||
dataStr = dataStr.substr(0, dataStr.lastIndexOf('&')); |
|||
} |
|||
requestInfo.url = requestInfo.url + '?' + dataStr; |
|||
let callbackName = "callback" + Math.ceil(Math.random() * 1000000); |
|||
// #ifdef H5
|
|||
window[callbackName] = function(data) { |
|||
resolve(data); |
|||
} |
|||
let script = document.createElement("script"); |
|||
script.src = requestInfo.url + "&callback=" + callbackName; |
|||
document.head.appendChild(script); |
|||
// 及时删除,防止加载过多的JS
|
|||
document.head.removeChild(script); |
|||
// #endif
|
|||
}); |
|||
} |
@ -0,0 +1,7 @@ |
|||
/***************纯粹的数据请求(如果使用这种可以删除掉fileUpload.js)******************/ |
|||
// import request from "./core/request.js";
|
|||
// export default request;
|
|||
|
|||
/********数据请求同时继承了文件上传(包括七牛云上传)************/ |
|||
import upload from "./upload/upload.js"; |
|||
export default upload; |
@ -0,0 +1,313 @@ |
|||
# request请求、配置简单、批量上传图片、视频、超强适应性(支持多域名请求) |
|||
1. 配置简单、源码清晰注释多、适用于一项目多域名请求、第三方请求、七牛云图片上传、本地服务器图片上传等等 |
|||
2. 支持请求`get`、`post`、`put`、`delete` |
|||
3. 自动显示请求加载动画(可单个接口关闭) |
|||
4. 全局`api`数据处理函数,只回调请求正确的数据(可单个接口关闭) |
|||
5. 未登录或登录失效自动拦截并调用登录方法(可单个接口关闭) |
|||
6. 全局自动提示接口抛出的错误信息(可单个接口关闭) |
|||
7. 支持 Promise |
|||
8. 支持拦截器 |
|||
9. 支持七牛云文件(图片、视频)批量上传 |
|||
10. 支持本地服务器文件(图片、视频)批量上传 |
|||
11. 支持上传文件拦截过滤 |
|||
12. 支持上传文件进度监听 |
|||
13. 支持上传文件单张成功回调 |
|||
|
|||
### 常见问题 |
|||
1.接口请求成功了,没有返回数据或者数据是走的catch回调 |
|||
|
|||
答:`requestConfig.js` 请求配置文件里面,有一个`$http.dataFactory`方法,里面写的只是参考示例,`此方法需要开发者根据各自的接口返回类型修改` |
|||
|
|||
2.官方的方法有数据,本插件方法请求报错跨域问题 |
|||
|
|||
答:`requestConfig.js` 请求配置文件里面,`header`请求头设置的`content-type`请求类型需求和后台保持一致 |
|||
|
|||
3.登录后用户`token`怎么设置? |
|||
|
|||
答:`requestConfig.js` 请求配置文件里面,`$http.requestStart`请求开始拦截器里面设置 |
|||
|
|||
4.怎么判断上传的文件(图片)太大?怎么过滤掉太大的文件(图片)? |
|||
|
|||
答:`requestConfig.js` 请求配置文件里面,`$http.requestStart`请求开始拦截器里面设置 |
|||
|
|||
### 本次更新注意事项 |
|||
1. 所有的headers都改成了header(和官方统一) |
|||
2. 七牛云的获取token等信息提取到了`requestConfig.js`文件,参考如下 |
|||
|
|||
``` |
|||
// 添加获取七牛云token的方法 |
|||
$http.getQnToken = function(callback){ |
|||
//该地址需要开发者自行配置(每个后台的接口风格都不一样) |
|||
$http.get("api/kemean/aid/qn_upload").then(data => { |
|||
/* |
|||
*接口返回参数: |
|||
*visitPrefix:访问文件的域名 |
|||
*token:七牛云上传token |
|||
*folderPath:上传的文件夹 |
|||
*region: 地区 默认为:SCN |
|||
*/ |
|||
callback({ |
|||
visitPrefix: data.visitPrefix, |
|||
token: data.token, |
|||
folderPath: data.folderPath, |
|||
region: "SCN" |
|||
}); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
### 文件说明 |
|||
1. `request => core` 请求方法的目录 |
|||
2. `request => core => request.js` 请求方法的class文件 |
|||
3. `request => core => utils.js` 请求方法的源码文件 |
|||
4. `request => upload` 上传方法的目录 |
|||
5. `request => upload => upload.js` 上传方法的class文件 |
|||
6. `request => upload => utils.js` 上传方法源码文件 |
|||
7. `request => upload => qiniuUploader.js` 七牛云官方上传文件 |
|||
8. `request => index.js` 输出方法的文件 |
|||
9. `requestConfig.js` 请求配置文件(具体看代码) |
|||
|
|||
### 在main.js引入并挂在Vue上 |
|||
``` |
|||
import $http from '@/zhouWei-request/requestConfig'; |
|||
Vue.prototype.$http = $http; |
|||
``` |
|||
|
|||
### 通用请求方法 |
|||
``` |
|||
this.$http.request({ |
|||
url: 'aid/region', |
|||
method: "GET", // POST、GET、PUT、DELETE、JSONP,具体说明查看官方文档 |
|||
data: {pid:0}, |
|||
timeout: 30000, // 默认 30000 说明:超时时间,单位 ms,具体说明查看官方文档 |
|||
dataType: "json", // 默认 json 说明:如果设为 json,会尝试对返回的数据做一次 JSON.parse,具体说明查看官方文档 |
|||
responseType: "text", // 默认 text 说明:设置响应的数据类型。合法值:text、arraybuffer,具体说明查看官方文档 |
|||
withCredentials: false, // 默认 false 说明:跨域请求时是否携带凭证(cookies),具体说明查看官方文档 |
|||
isPrompt: true,//(默认 true 说明:本接口抛出的错误是否提示) |
|||
load: true,//(默认 true 说明:本接口是否提示加载动画) |
|||
header: { //默认 无 说明:请求头 |
|||
'Content-Type': 'application/json' |
|||
}, |
|||
isFactory: true, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数将失去作用) |
|||
}).then(function (response) { |
|||
//这里只会在接口是成功状态返回 |
|||
}).catch(function (error) { |
|||
//这里只会在接口是失败状态返回,不需要去处理错误提示 |
|||
console.log(error); |
|||
}); |
|||
``` |
|||
|
|||
### get请求 正常写法 |
|||
``` |
|||
this.$http.get('aid/region',{pid:0}). |
|||
then(function (response) { |
|||
//这里只会在接口是成功状态返回 |
|||
}).catch(function (error) { |
|||
//这里只会在接口是失败状态返回,不需要去处理错误提示 |
|||
console.log(error); |
|||
}); |
|||
``` |
|||
|
|||
### post请求 async写法 |
|||
``` |
|||
async request(){ |
|||
let data = await this.$http.post('aid/region',{pid:0}); |
|||
console.log(data); |
|||
} |
|||
``` |
|||
|
|||
### 其他功能配置项 |
|||
``` |
|||
let data = await this.$http.post( |
|||
'http://www.aaa.com/aid/region', //可以直接放链接(将不启用全局定义域名) |
|||
{ |
|||
pid:0 |
|||
}, |
|||
{ |
|||
isPrompt: true,//(默认 true 说明:本接口抛出的错误是否提示) |
|||
load: true,//(默认 true 说明:本接口是否提示加载动画) |
|||
header: { //默认 无 说明:请求头 |
|||
'Content-Type': 'application/json' |
|||
}, |
|||
isFactory: true //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数将失去作用) |
|||
} |
|||
); |
|||
``` |
|||
|
|||
### `requestConfig.js`可以设置服务器上传图片默认url |
|||
``` |
|||
//可以new多个request来支持多个域名请求 |
|||
let $http = new request({ |
|||
//服务器本地上传文件地址 |
|||
fileUrl: base.baseUrl, |
|||
// 服务器上传图片默认url |
|||
defaultUploadUrl: "api/common/v1/upload_image", |
|||
}); |
|||
``` |
|||
``` |
|||
// 上传可以不用传递url(使用全局的上传图片url) |
|||
this.$http.urlImgUpload({ |
|||
name:"后台接受文件key名称", //默认 file |
|||
count:"最大选择数",//默认 9 |
|||
sizeType:"选择压缩图原图,默认两个都选",//默认 ['original', 'compressed'] |
|||
sourceType:"选择相机拍照或相册上传 默认两个都选",//默认 ['album','camera'] |
|||
data:"而外参数" //可不填, |
|||
}); |
|||
// 上传可以不用传递url(使用全局的上传图片url) |
|||
this.$http.urlVideoUpload({ |
|||
sourceType:"选择相机拍照或相册上传 默认两个都选",//默认 ['album','camera'] |
|||
compressed:"是否压缩所选的视频源文件,默认值为 true,需要压缩",//默认 false |
|||
maxDuration: "拍摄视频最长拍摄时间,单位秒。最长支持 60 秒", //默认 60 |
|||
camera: '前置还是后置摄像头', //'front'、'back',默认'back' |
|||
name:"后台接受文件key名称", //默认 file |
|||
data:"而外参数" //可不填, |
|||
}); |
|||
// 上传可以不用传递url(使用全局的上传图片url) |
|||
this.$http.urlFileUpload({ |
|||
files: [], // 必填 临时文件路径 格式: [{path: "图片地址"}] |
|||
data:"向服务器传递的参数", //可不填 |
|||
name:"后台接受文件key名称", //默认 file |
|||
}); |
|||
``` |
|||
|
|||
### 本地服务器图片上传(支持多张上传) |
|||
``` |
|||
this.$http.urlImgUpload('flie/upload',{ |
|||
name:"后台接受文件key名称", //默认 file |
|||
count:"最大选择数",//默认 9 |
|||
sizeType:"选择压缩图原图,默认两个都选",//默认 ['original', 'compressed'] |
|||
sourceType:"选择相机拍照或相册上传 默认两个都选",//默认 ['album','camera'] |
|||
data:"而外参数" //可不填, |
|||
isPrompt: true,//(默认 true 说明:本接口抛出的错误是否提示) |
|||
load: true,//(默认 true 说明:本接口是否提示加载动画) |
|||
header: { //默认 无 说明:请求头 |
|||
'Content-Type': 'application/json' |
|||
}, |
|||
isFactory: true, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数奖失去作用) |
|||
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制) |
|||
onSelectComplete: res => { |
|||
console.log("选择完成返回:",res); |
|||
}, |
|||
onEachUpdate: res => { |
|||
console.log("单张上传成功返回:",res); |
|||
}, |
|||
onProgressUpdate: res => { |
|||
console.log("上传进度返回:",res); |
|||
} |
|||
}).then(res => { |
|||
console.log("全部上传完返回结果:",res); |
|||
}); |
|||
``` |
|||
### 本地服务器视频上传 |
|||
``` |
|||
this.$http.urlVideoUpload('flie/upload',{ |
|||
sourceType:"选择相机拍照或相册上传 默认两个都选",//默认 ['album','camera'] |
|||
compressed:"是否压缩所选的视频源文件,默认值为 true,需要压缩",//默认 false |
|||
maxDuration: "拍摄视频最长拍摄时间,单位秒。最长支持 60 秒", //默认 60 |
|||
camera: '前置还是后置摄像头', //'front'、'back',默认'back' |
|||
name:"后台接受文件key名称", //默认 file |
|||
data:"而外参数" //可不填, |
|||
isPrompt: true,//(默认 true 说明:本接口抛出的错误是否提示) |
|||
load: true,//(默认 true 说明:本接口是否提示加载动画) |
|||
header: { //默认 无 说明:请求头 |
|||
'Content-Type': 'application/json' |
|||
}, |
|||
isFactory: true, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数奖失去作用) |
|||
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制) |
|||
onProgressUpdate: res => { |
|||
console.log("上传进度返回:",res); |
|||
}, |
|||
onSelectComplete: res => { |
|||
console.log("选择完成返回:",res); |
|||
}, |
|||
}).then(res => { |
|||
console.log("全部上传完返回结果:",res); |
|||
}); |
|||
``` |
|||
### 本地服务器文件上传(支持多张上传) |
|||
``` |
|||
this.$http.urlFileUpload("flie/upload",{ |
|||
files: [], // 必填 临时文件路径 格式: [{path: "图片地址"}] |
|||
data:"向服务器传递的参数", //可不填 |
|||
name:"后台接受文件key名称", //默认 file |
|||
isPrompt: true,//(默认 true 说明:本接口抛出的错误是否提示) |
|||
load: true,//(默认 true 说明:本接口是否提示加载动画) |
|||
header: { //默认 无 说明:请求头 |
|||
'Content-Type': 'application/json' |
|||
}, |
|||
isFactory: true, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数奖失去作用) |
|||
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制) |
|||
onEachUpdate: res => { |
|||
console.log("单张上传成功返回:",res); |
|||
}, |
|||
onProgressUpdate: res => { |
|||
console.log("上传进度返回:",res); |
|||
} |
|||
}).then(res => { |
|||
console.log("全部上传完返回结果:",res); |
|||
}); |
|||
``` |
|||
|
|||
### 七牛云图片上传(支持多张上传) |
|||
``` |
|||
this.$http.qnImgUpload({ |
|||
count:"最大选择数", // 默认 9 |
|||
sizeType:"选择压缩图原图,默认两个都选", // 默认 ['original', 'compressed'] |
|||
sourceType:"选择相机拍照或相册上传 默认两个都选", // 默认 ['album','camera'] |
|||
load: true, //(默认 true 说明:本接口是否提示加载动画) |
|||
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制) |
|||
onSelectComplete: res => { |
|||
console.log("选择完成返回:",res); |
|||
}, |
|||
onEachUpdate: res => { |
|||
console.log("单张上传成功返回:",res); |
|||
}, |
|||
onProgressUpdate: res => { |
|||
console.log("上传进度返回:",res); |
|||
} |
|||
}).then(res => { |
|||
console.log("全部上传完返回结果:",res); |
|||
}); |
|||
``` |
|||
### 七牛云视频上传 |
|||
``` |
|||
this.$http.qnVideoUpload({ |
|||
sourceType:"选择相机拍照或相册上传 默认两个都选",//默认 ['album','camera'] |
|||
compressed:"是否压缩所选的视频源文件,默认值为 true,需要压缩",//默认 false |
|||
maxDuration: "拍摄视频最长拍摄时间,单位秒。最长支持 60 秒", //默认 60 |
|||
camera: '前置还是后置摄像头', //'front'、'back',默认'back' |
|||
load: true,//(默认 true 说明:本接口是否提示加载动画) |
|||
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制) |
|||
onSelectComplete: res => { |
|||
console.log("选择完成返回:",res); |
|||
}, |
|||
onProgressUpdate: res => { |
|||
console.log("上传进度返回:",res); |
|||
} |
|||
}).then(res => { |
|||
console.log("全部上传完返回结果:",res); |
|||
}); |
|||
``` |
|||
### 七牛云文件上传(支持多张上传) |
|||
``` |
|||
this.$http.qnFileUpload( |
|||
{ |
|||
files:[], // 必填 临时文件路径 格式: [{path: "图片地址"}] |
|||
load: true, //(默认 true 说明:本接口是否提示加载动画) |
|||
maxSize: 300000, //(默认 无 说明:上传的文件最大字节数限制,默认不限制) |
|||
onEachUpdate: res => { |
|||
console.log("单张上传成功返回:",res); |
|||
}, |
|||
onProgressUpdate: res => { |
|||
console.log("上传进度返回:",res); |
|||
} |
|||
}).then(res => { |
|||
console.log("全部上传完返回结果:",res); |
|||
}); |
|||
``` |
|||
### jsonp 跨域请求(只支持H5) |
|||
``` |
|||
let data = await this.$http.jsonp('http://www.aaa.com/aid/region',{pid:0}, { |
|||
isFactory: false, //(默认 true 说明:本接口是否调用公共的数据处理方法,设置false后isPrompt参数奖失去作用) |
|||
}); |
|||
``` |
@ -0,0 +1,169 @@ |
|||
// created by gpake
|
|||
(function () { |
|||
|
|||
var config = { |
|||
qiniuRegion: '', |
|||
qiniuImageURLPrefix: '', |
|||
qiniuUploadToken: '', |
|||
qiniuUploadTokenURL: '', |
|||
qiniuUploadTokenFunction: null, |
|||
qiniuShouldUseQiniuFileName: false |
|||
} |
|||
|
|||
module.exports = { |
|||
init: init, |
|||
upload: upload, |
|||
} |
|||
|
|||
// 在整个程序生命周期中,只需要 init 一次即可
|
|||
// 如果需要变更参数,再调用 init 即可
|
|||
function init(options) { |
|||
config = { |
|||
qiniuRegion: '', |
|||
qiniuImageURLPrefix: '', |
|||
qiniuUploadToken: '', |
|||
qiniuUploadTokenURL: '', |
|||
qiniuUploadTokenFunction: null, |
|||
qiniuShouldUseQiniuFileName: false |
|||
}; |
|||
updateConfigWithOptions(options); |
|||
} |
|||
|
|||
function updateConfigWithOptions(options) { |
|||
if (options.region) { |
|||
config.qiniuRegion = options.region; |
|||
} else { |
|||
console.error('qiniu uploader need your bucket region'); |
|||
} |
|||
if (options.uptoken) { |
|||
config.qiniuUploadToken = options.uptoken; |
|||
} else if (options.uptokenURL) { |
|||
config.qiniuUploadTokenURL = options.uptokenURL; |
|||
} else if (options.uptokenFunc) { |
|||
config.qiniuUploadTokenFunction = options.uptokenFunc; |
|||
} |
|||
if (options.domain) { |
|||
config.qiniuImageURLPrefix = options.domain; |
|||
} |
|||
config.qiniuShouldUseQiniuFileName = options.shouldUseQiniuFileName |
|||
} |
|||
|
|||
function upload(filePath, success, fail, options, progress, cancelTask) { |
|||
if (null == filePath) { |
|||
console.error('qiniu uploader need filePath to upload'); |
|||
return; |
|||
} |
|||
if (options) { |
|||
updateConfigWithOptions(options); |
|||
} |
|||
if (config.qiniuUploadToken) { |
|||
doUpload(filePath, success, fail, options, progress, cancelTask); |
|||
} else if (config.qiniuUploadTokenURL) { |
|||
getQiniuToken(function () { |
|||
doUpload(filePath, success, fail, options, progress, cancelTask); |
|||
}); |
|||
} else if (config.qiniuUploadTokenFunction) { |
|||
config.qiniuUploadToken = config.qiniuUploadTokenFunction(); |
|||
if (null == config.qiniuUploadToken && config.qiniuUploadToken.length > 0) { |
|||
console.error('qiniu UploadTokenFunction result is null, please check the return value'); |
|||
return |
|||
} |
|||
doUpload(filePath, success, fail, options, progress, cancelTask); |
|||
} else { |
|||
console.error('qiniu uploader need one of [uptoken, uptokenURL, uptokenFunc]'); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
function doUpload(filePath, success, fail, options, progress, cancelTask) { |
|||
if (null == config.qiniuUploadToken && config.qiniuUploadToken.length > 0) { |
|||
console.error('qiniu UploadToken is null, please check the init config or networking'); |
|||
return |
|||
} |
|||
var url = uploadURLFromRegionCode(config.qiniuRegion); |
|||
var fileName = filePath.split('//')[1]; |
|||
if (options && options.key) { |
|||
fileName = options.key; |
|||
} |
|||
var formData = { |
|||
'token': config.qiniuUploadToken |
|||
}; |
|||
if (!config.qiniuShouldUseQiniuFileName) { |
|||
formData['key'] = fileName |
|||
} |
|||
var uploadTask = wx.uploadFile({ |
|||
url: url, |
|||
filePath: filePath, |
|||
name: 'file', |
|||
formData: formData, |
|||
success: function (res) { |
|||
var dataString = res.data |
|||
if (res.data.hasOwnProperty('type') && res.data.type === 'Buffer') { |
|||
dataString = String.fromCharCode.apply(null, res.data.data) |
|||
} |
|||
try { |
|||
var dataObject = JSON.parse(dataString); |
|||
//do something
|
|||
var imageUrl = config.qiniuImageURLPrefix + '/' + dataObject.key; |
|||
dataObject.imageURL = imageUrl; |
|||
if (success) { |
|||
success(dataObject); |
|||
} |
|||
} catch (e) { |
|||
console.log('parse JSON failed, origin String is: ' + dataString) |
|||
if (fail) { |
|||
fail(e); |
|||
} |
|||
} |
|||
}, |
|||
fail: function (error) { |
|||
console.error(error); |
|||
if (fail) { |
|||
fail(error); |
|||
} |
|||
} |
|||
}) |
|||
|
|||
uploadTask.onProgressUpdate((res) => { |
|||
progress && progress(res) |
|||
}) |
|||
|
|||
cancelTask && cancelTask(() => { |
|||
uploadTask.abort() |
|||
}) |
|||
} |
|||
|
|||
function getQiniuToken(callback) { |
|||
wx.request({ |
|||
url: config.qiniuUploadTokenURL, |
|||
success: function (res) { |
|||
var token = res.data.uptoken; |
|||
if (token && token.length > 0) { |
|||
config.qiniuUploadToken = token; |
|||
if (callback) { |
|||
callback(); |
|||
} |
|||
} else { |
|||
console.error('qiniuUploader cannot get your token, please check the uptokenURL or server') |
|||
} |
|||
}, |
|||
fail: function (error) { |
|||
console.error('qiniu UploadToken is null, please check the init config or networking: ' + error); |
|||
} |
|||
}) |
|||
} |
|||
|
|||
function uploadURLFromRegionCode(code) { |
|||
var uploadURL = null; |
|||
switch (code) { |
|||
case 'ECN': uploadURL = 'https://up.qbox.me'; break; |
|||
case 'NCN': uploadURL = 'https://up-z1.qbox.me'; break; |
|||
case 'SCN': uploadURL = 'https://up-z2.qbox.me'; break; |
|||
case 'NA': uploadURL = 'https://up-na0.qbox.me'; break; |
|||
case 'ASG': uploadURL = 'https://up-as0.qbox.me'; break; |
|||
default: console.error('please make the region is with one of [ECN, SCN, NCN, NA, ASG]'); |
|||
} |
|||
return uploadURL; |
|||
} |
|||
|
|||
})(); |
@ -0,0 +1,214 @@ |
|||
import request from "./../core/request.js"; |
|||
const { |
|||
chooseImage, |
|||
chooseVideo, |
|||
qiniuUpload, |
|||
urlUpload |
|||
} = require("./utils"); |
|||
import { |
|||
mergeConfig |
|||
} from "./../core/utils.js"; |
|||
export default class fileUpload extends request { |
|||
constructor(props) { |
|||
// 调用实现父类的构造函数
|
|||
super(props); |
|||
} |
|||
//七牛云上传图片
|
|||
async qnImgUpload(options = {}) { |
|||
let files; |
|||
try { |
|||
files = await chooseImage(options); |
|||
// 选择完成回调
|
|||
options.onSelectComplete && options.onSelectComplete(files); |
|||
} catch (err) { |
|||
this.requestError && this.requestError(err); |
|||
return Promise.reject(err); |
|||
} |
|||
if (files) { |
|||
return this.qnFileUpload({ |
|||
...options, |
|||
files: files |
|||
}); |
|||
} |
|||
} |
|||
//七牛云上传视频
|
|||
async qnVideoUpload(options = {}) { |
|||
let files; |
|||
try { |
|||
files = await chooseVideo(options); |
|||
// 选择完成回调
|
|||
options.onSelectComplete && options.onSelectComplete(files); |
|||
} catch (err) { |
|||
this.requestError && this.requestError(err); |
|||
return Promise.reject(err); |
|||
} |
|||
if (files) { |
|||
return this.qnFileUpload({ |
|||
...options, |
|||
files: files |
|||
}); |
|||
} |
|||
} |
|||
|
|||
//七牛云文件上传(支持多张上传)
|
|||
async qnFileUpload(options = {}) { |
|||
let requestInfo; |
|||
try { |
|||
// 数据合并
|
|||
requestInfo = { |
|||
...this.config, |
|||
...options, |
|||
header: {}, |
|||
method: "FILE" |
|||
}; |
|||
//请求前回调
|
|||
if (this.requestStart) { |
|||
let requestStart = this.requestStart(requestInfo); |
|||
if (typeof requestStart == "object") { |
|||
let changekeys = ["load", "files"]; |
|||
changekeys.forEach(key => { |
|||
requestInfo[key] = requestStart[key]; |
|||
}); |
|||
} else { |
|||
throw { |
|||
errMsg: "【request】请求开始拦截器未通过", |
|||
statusCode: 0, |
|||
data: requestInfo.data, |
|||
method: requestInfo.method, |
|||
header: requestInfo.header, |
|||
url: requestInfo.url, |
|||
} |
|||
} |
|||
} |
|||
let requestResult = await qiniuUpload(requestInfo, this.getQnToken); |
|||
return Promise.resolve(requestResult); |
|||
} catch (err) { |
|||
this.requestError && this.requestError(err); |
|||
return Promise.reject(err); |
|||
} finally { |
|||
this.requestEnd && this.requestEnd(requestInfo); |
|||
} |
|||
} |
|||
//本地服务器图片上传
|
|||
async urlImgUpload() { |
|||
let options = {}; |
|||
if (arguments[0]) { |
|||
if (typeof(arguments[0]) == "string") { |
|||
options.url = arguments[0]; |
|||
} else if (typeof(arguments[0]) == "object") { |
|||
options = Object.assign(options, arguments[0]); |
|||
} |
|||
} |
|||
if (arguments[1] && typeof(arguments[1]) == "object") { |
|||
options = Object.assign(options, arguments[1]); |
|||
} |
|||
try { |
|||
options.files = await chooseImage(options); |
|||
// 选择完成回调
|
|||
options.onSelectComplete && options.onSelectComplete(options.files); |
|||
} catch (err) { |
|||
this.requestError && this.requestError(err); |
|||
return Promise.reject(err); |
|||
} |
|||
if (options.files) { |
|||
return this.urlFileUpload(options); |
|||
} |
|||
} |
|||
//本地服务器上传视频
|
|||
async urlVideoUpload() { |
|||
let options = {}; |
|||
if (arguments[0]) { |
|||
if (typeof(arguments[0]) == "string") { |
|||
options.url = arguments[0]; |
|||
} else if (typeof(arguments[0]) == "object") { |
|||
options = Object.assign(options, arguments[0]); |
|||
} |
|||
} |
|||
if (arguments[1] && typeof(arguments[1]) == "object") { |
|||
options = Object.assign(options, arguments[1]); |
|||
} |
|||
try { |
|||
options.files = await chooseVideo(options); |
|||
console.log('files888888',options.files); |
|||
// 选择完成回调
|
|||
options.onSelectComplete && options.onSelectComplete(options.files); |
|||
} catch (err) { |
|||
this.requestError && this.requestError(err); |
|||
return Promise.reject(err); |
|||
} |
|||
if (options.files) { |
|||
return this.urlFileUpload(options); |
|||
} |
|||
} |
|||
//本地服务器文件上传方法
|
|||
async urlFileUpload() { |
|||
console.log('urlFileUpload'); |
|||
let requestInfo = { |
|||
method: "FILE" |
|||
}; |
|||
if (arguments[0]) { |
|||
if (typeof(arguments[0]) == "string") { |
|||
requestInfo.url = arguments[0]; |
|||
} else if (typeof(arguments[0]) == "object") { |
|||
requestInfo = Object.assign(requestInfo, arguments[0]); |
|||
} |
|||
} |
|||
if (arguments[1] && typeof(arguments[1]) == "object") { |
|||
requestInfo = Object.assign(requestInfo, arguments[1]); |
|||
} |
|||
if (!requestInfo.url && this.defaultUploadUrl) { |
|||
requestInfo.url = this.defaultUploadUrl; |
|||
} |
|||
// 请求数据
|
|||
// 是否运行过请求开始钩子
|
|||
let runRequestStart = false; |
|||
try { |
|||
if (!requestInfo.url) { |
|||
throw { |
|||
errMsg: "【request】文件上传缺失数据url", |
|||
statusCode: 0, |
|||
data: requestInfo.data, |
|||
method: requestInfo.method, |
|||
header: requestInfo.header, |
|||
url: requestInfo.url, |
|||
} |
|||
} |
|||
// 数据合并
|
|||
requestInfo = mergeConfig(this, requestInfo); |
|||
// 代表之前运行到这里
|
|||
runRequestStart = true; |
|||
//请求前回调
|
|||
if (this.requestStart) { |
|||
let requestStart = this.requestStart(requestInfo); |
|||
if (typeof requestStart == "object") { |
|||
console.log('objects789'); |
|||
let changekeys = ["data", "header", "isPrompt", "load", "isFactory", "files"]; |
|||
changekeys.forEach(key => { |
|||
requestInfo[key] = requestStart[key]; |
|||
}); |
|||
console.log('objects78910111213141516 requestInfo',requestInfo); |
|||
} else { |
|||
throw { |
|||
errMsg: "【request】请求开始拦截器未通过", |
|||
statusCode: 0, |
|||
data: requestInfo.data, |
|||
method: requestInfo.method, |
|||
header: requestInfo.header, |
|||
url: requestInfo.url, |
|||
} |
|||
} |
|||
} |
|||
console.log('789465'); |
|||
let requestResult = await urlUpload(requestInfo, this.dataFactory); |
|||
console.log('321456',requestResult); |
|||
return Promise.resolve(requestResult); |
|||
} catch (err) { |
|||
this.requestError && this.requestError(err); |
|||
return Promise.reject(err); |
|||
} finally { |
|||
if (runRequestStart) { |
|||
this.requestEnd && this.requestEnd(requestInfo); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,234 @@ |
|||
const qiniuUploader = require("./qiniuUploader"); |
|||
//七牛云上传文件命名
|
|||
export const randomChar = function(l, url = "") { |
|||
const x = "0123456789qwertyuioplkjhgfdsazxcvbnm"; |
|||
let tmp = ""; |
|||
let time = new Date(); |
|||
for (let i = 0; i < l; i++) { |
|||
tmp += x.charAt(Math.ceil(Math.random() * 100000000) % x.length); |
|||
} |
|||
return ( |
|||
"file/" + |
|||
url + |
|||
time.getTime() + |
|||
tmp |
|||
); |
|||
} |
|||
//图片选择
|
|||
export const chooseImage = function(data) { |
|||
return new Promise((resolve, reject) => { |
|||
uni.chooseImage({ |
|||
count: data.count || 9, //默认9
|
|||
sizeType: data.sizeType || ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
|
|||
sourceType: data.sourceType || ['album', 'camera'], //从相册选择
|
|||
success: function(res) { |
|||
resolve(res.tempFiles); |
|||
}, |
|||
fail: err => { |
|||
reject(err); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
//视频选择
|
|||
export const chooseVideo = function(data) { |
|||
return new Promise((resolve, reject) => { |
|||
uni.chooseVideo({ |
|||
sourceType: data.sourceType || ['album', 'camera'], //从相册选择
|
|||
compressed: data.compressed || false, //是否压缩所选的视频源文件,默认值为 true,需要压缩。
|
|||
maxDuration: data.maxDuration || 60, //拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。
|
|||
camera: data.camera || 'back', //'front'、'back',默认'back'
|
|||
success: function(res) { |
|||
let files = [{ |
|||
path: res.tempFilePath |
|||
}]; |
|||
// #ifdef APP-PLUS || H5 || MP-WEIXIN
|
|||
files[0].duration = res.duration; |
|||
files[0].size = res.size; |
|||
files[0].height = res.height; |
|||
files[0].width = res.width; |
|||
// #endif
|
|||
// #ifdef H5
|
|||
files[0].name = res.name; |
|||
// #endif
|
|||
resolve(files); |
|||
}, |
|||
fail: err => { |
|||
reject(err); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
// 七牛云上传
|
|||
export const qiniuUpload = function(requestInfo, getQnToken) { |
|||
return new Promise((resolve, reject) => { |
|||
if (Array.isArray(requestInfo.files)) { |
|||
let len = requestInfo.files.length; |
|||
let fileList = new Array; |
|||
if (getQnToken) { |
|||
getQnToken(qnRes => { |
|||
/* |
|||
*接口返回参数: |
|||
*visitPrefix:访问文件的域名 |
|||
*token:七牛云上传token |
|||
*folderPath:上传的文件夹 |
|||
*region: 地区 默认为:SCN |
|||
*/ |
|||
let prefixLen = qnRes.visitPrefix.length; |
|||
if(qnRes.visitPrefix.charAt(prefixLen - 1) == '/'){ |
|||
qnRes.visitPrefix = qnRes.visitPrefix.substring(0, prefixLen - 1) |
|||
} |
|||
uploadFile(0); |
|||
|
|||
function uploadFile(i) { |
|||
let item = requestInfo.files[i]; |
|||
let updateUrl = randomChar(10, qnRes.folderPath); |
|||
let fileData = { |
|||
fileIndex: i, |
|||
files: requestInfo.files, |
|||
...item |
|||
}; |
|||
if (item.name) { |
|||
fileData.name = item.name; |
|||
let nameArr = item.name.split("."); |
|||
updateUrl += "." + nameArr[nameArr.length - 1]; |
|||
} |
|||
// 交给七牛上传
|
|||
qiniuUploader.upload(item.path || item, (res) => { |
|||
fileData.url = res.imageURL; |
|||
requestInfo.onEachUpdate && requestInfo.onEachUpdate({ |
|||
url: res.imageURL, |
|||
...fileData |
|||
}); |
|||
fileList.push(res.imageURL); |
|||
if (len - 1 > i) { |
|||
uploadFile(i + 1); |
|||
} else { |
|||
resolve(fileList); |
|||
} |
|||
}, (error) => { |
|||
reject(error); |
|||
}, { |
|||
region: qnRes.region || 'SCN', //地区
|
|||
domain: qnRes.visitPrefix, // bucket 域名,下载资源时用到。
|
|||
key: updateUrl, |
|||
uptoken: qnRes.token, // 由其他程序生成七牛 uptoken
|
|||
uptokenURL: 'UpTokenURL.com/uptoken' // 上传地址
|
|||
}, (res) => { |
|||
console.log(requestInfo); |
|||
requestInfo.onProgressUpdate && requestInfo.onProgressUpdate(Object.assign({}, fileData, res)); |
|||
// console.log('上传进度', res.progress)
|
|||
// console.log('已经上传的数据长度', res.totalBytesSent)
|
|||
// console.log('预期需要上传的数据总长度', res.totalBytesExpectedToSend)
|
|||
}); |
|||
} |
|||
}); |
|||
} else { |
|||
reject({ |
|||
errMsg: "请添加七牛云回调方法:getQnToken", |
|||
statusCode: 0 |
|||
}); |
|||
} |
|||
} else { |
|||
reject({ |
|||
errMsg: "files 必须是数组类型", |
|||
statusCode: 0 |
|||
}); |
|||
}; |
|||
}); |
|||
} |
|||
// 服务器URL上传
|
|||
export const urlUpload = function(requestInfo, dataFactory) { |
|||
console.log('urlUpload',requestInfo); |
|||
return new Promise((resolve, reject) => { |
|||
// 本地文件上传去掉默认Content-Type
|
|||
if (requestInfo.header['Content-Type']) { |
|||
delete requestInfo.header['Content-Type']; |
|||
} |
|||
// 本地文件上传去掉默认Content-Type
|
|||
if (requestInfo.header['content-type']) { |
|||
delete requestInfo.header['content-type']; |
|||
} |
|||
if (Array.isArray(requestInfo.files)) { |
|||
console.log('requestInfo.files',requestInfo.files); |
|||
|
|||
// #ifdef MP
|
|||
console.log('456mp') |
|||
const len = requestInfo.files.length - 1; |
|||
let fileList = new Array; |
|||
fileUpload(0); |
|||
|
|||
function fileUpload(i) { |
|||
console.log('fileUpload'); |
|||
let item = requestInfo.files[i]; |
|||
let fileData = { |
|||
fileIndex: i, |
|||
files: requestInfo.files, |
|||
...item |
|||
}; |
|||
let config = { |
|||
url: requestInfo.url, |
|||
filePath: item.path, |
|||
files:requestInfo.files, |
|||
header: requestInfo.header, //加入请求头
|
|||
name: requestInfo.name || "file", |
|||
success: (response) => { |
|||
console.log('456123'); |
|||
//是否用外部的数据处理方法
|
|||
if (requestInfo.isFactory && dataFactory) { |
|||
console.log('waibu'); |
|||
//数据处理
|
|||
dataFactory({ |
|||
...requestInfo, |
|||
response: response, |
|||
}).then(data => { |
|||
console.log('data888',data); |
|||
fileList.push(data); |
|||
requestInfo.onEachUpdate && requestInfo.onEachUpdate({ |
|||
data: data, |
|||
...fileData |
|||
}); |
|||
if (len <= i) { |
|||
resolve(fileList); |
|||
} else { |
|||
fileUpload(i + 1); |
|||
} |
|||
},err => { |
|||
reject(err); |
|||
}); |
|||
} else { |
|||
console.log('nb'); |
|||
requestInfo.onEachUpdate && requestInfo.onEachUpdate({ |
|||
data: response, |
|||
...fileData |
|||
}); |
|||
fileList.push(response); |
|||
if (len <= i) { |
|||
resolve(fileList); |
|||
} else { |
|||
fileUpload(i + 1); |
|||
} |
|||
} |
|||
}, |
|||
fail: (err) => { |
|||
reject(err); |
|||
} |
|||
}; |
|||
if (requestInfo.data) { |
|||
config.formData = requestInfo.data; |
|||
} |
|||
const uploadTask = uni.uploadFile(config); |
|||
uploadTask.onProgressUpdate(res => { |
|||
requestInfo.onProgressUpdate && requestInfo.onProgressUpdate(Object.assign({}, fileData, res)); |
|||
}); |
|||
} |
|||
// #endif
|
|||
} else { |
|||
reject({ |
|||
errMsg: "files 必须是数组类型", |
|||
statusCode: 0 |
|||
}); |
|||
} |
|||
}); |
|||
console.log('urlUploadEND'); |
|||
} |
@ -0,0 +1,73 @@ |
|||
// 路由
|
|||
import { |
|||
RouterMount, |
|||
createRouter |
|||
} from './uni-simple-router.js' |
|||
import store from '@/nxTemp/store' |
|||
const router = createRouter({ |
|||
platform: process.env.VUE_APP_PLATFORM, |
|||
applet: { |
|||
animationDuration: 0 //默认 300ms
|
|||
}, |
|||
routerErrorEach: ({ |
|||
type, |
|||
msg |
|||
}) => { |
|||
console.log(55555,type,msg) |
|||
|
|||
switch (type) { |
|||
case 3: // APP退出应用
|
|||
// #ifdef APP-PLUS
|
|||
router.$lockStatus = false; |
|||
uni.showModal({ |
|||
title: '提示', |
|||
content: '您确定要退出应用吗?', |
|||
success: function(res) { |
|||
if (res.confirm) { |
|||
plus.runtime.quit(); |
|||
} |
|||
} |
|||
}); |
|||
// #endif
|
|||
break; |
|||
case 2: |
|||
case 0: |
|||
console.log(66666,type) |
|||
router.$lockStatus = false; |
|||
break; |
|||
default: |
|||
console.log(77777,type) |
|||
break; |
|||
} |
|||
|
|||
}, |
|||
// 通配符,非定义页面,跳转404
|
|||
routes: [...ROUTES, |
|||
{ |
|||
path: '*', |
|||
redirect: (to) => { |
|||
return { |
|||
name: '404' |
|||
} |
|||
} |
|||
}, |
|||
] |
|||
}); |
|||
|
|||
//全局路由前置守卫
|
|||
router.beforeEach((to, from, next) => { |
|||
// 权限控制登录
|
|||
next() |
|||
// // 有两个个判断条件,一个是token,还有一个路由元信息
|
|||
// let userInfo = Boolean(uni.getStorageSync('userInfo'));
|
|||
// // 权限控制
|
|||
// if (to.meta && to.meta.auth && !userInfo) {//没有登录信息执行登录操作
|
|||
// } else {
|
|||
// next()
|
|||
// }
|
|||
}); |
|||
|
|||
export { |
|||
router, |
|||
RouterMount |
|||
} |
1
nxTemp/router/uni-simple-router.js
File diff suppressed because it is too large
View File
@ -0,0 +1,19 @@ |
|||
import Vue from "vue"; |
|||
import Vuex from "vuex"; |
|||
|
|||
Vue.use(Vuex); |
|||
const files = require.context("./modules", false, /\.js$/); |
|||
let modules = { |
|||
state: {}, |
|||
mutations: {}, |
|||
actions: {} |
|||
}; |
|||
|
|||
files.keys().forEach((key) => { |
|||
Object.assign(modules.state, files(key)["state"]); |
|||
Object.assign(modules.mutations, files(key)["mutations"]); |
|||
Object.assign(modules.actions, files(key)["actions"]); |
|||
}); |
|||
const store = new Vuex.Store(modules); |
|||
export default store; |
|||
|
@ -0,0 +1,67 @@ |
|||
// 用户数据模块
|
|||
import {router} from '@/nxTemp/router/index.js' |
|||
const TOKEN = uni.getStorageSync("token") || ""; |
|||
const OPENID = uni.getStorageSync("openId") || ""; |
|||
const USER_INFO = uni.getStorageSync("userInfo") || {}; |
|||
|
|||
export const state = { |
|||
// 前端token
|
|||
token: TOKEN, |
|||
// 用户openid
|
|||
openId: OPENID, |
|||
// 用户信息 头像 昵称
|
|||
userInfo: USER_INFO |
|||
} |
|||
|
|||
export const actions = { |
|||
async setUserData({ |
|||
state, |
|||
commit |
|||
}, data) { |
|||
commit('setStateAttr', { |
|||
key: 'userInfo', |
|||
val: data |
|||
}) |
|||
uni.setStorageSync('userInfo', data); |
|||
}, |
|||
// 登录过期 重新登录
|
|||
reLogin({ |
|||
commit |
|||
}, info) { |
|||
commit('setStateAttr', { |
|||
key: 'token', |
|||
val: '' |
|||
}) |
|||
uni.setStorageSync("token", ''); |
|||
//过期跳转登录页重新登录
|
|||
router.push({ |
|||
path: '/pages/login/login' |
|||
}); |
|||
} |
|||
} |
|||
|
|||
export const mutations = { |
|||
//更新state数据
|
|||
setStateAttr(state, param) { |
|||
if (param instanceof Array) { |
|||
for (let item of param) { |
|||
state[item.key] = item.val; |
|||
} |
|||
} else { |
|||
state[param.key] = param.val; |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
export const getters = { |
|||
// 用户是否登录
|
|||
hasLogin: state => { |
|||
if (state.token) { |
|||
return true; |
|||
} else { |
|||
return false |
|||
} |
|||
} |
|||
} |
|||
|
@ -0,0 +1,165 @@ |
|||
export default { |
|||
/** |
|||
* 图片处理-预览图片 |
|||
* @param {Array} urls - 图片列表 |
|||
* @param {Number} current - 首个预览下标 |
|||
*/ |
|||
previewImage(urls = [], current = 0) { |
|||
uni.previewImage({ |
|||
urls: urls, |
|||
current: current, |
|||
indicator: 'default', |
|||
loop: true, |
|||
fail(err) { |
|||
console.log('previewImage出错', urls, err) |
|||
}, |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 打电话 |
|||
* @param {String<Number>} phoneNumber - 数字字符串 |
|||
*/ |
|||
callPhone(phoneNumber = '') { |
|||
let num = phoneNumber.toString() |
|||
uni.makePhoneCall({ |
|||
phoneNumber: num, |
|||
fail(err) { |
|||
console.log('makePhoneCall出错', err) |
|||
}, |
|||
}); |
|||
}, |
|||
|
|||
/** |
|||
* @description: 弹窗封装 |
|||
* @param {String} msg |
|||
* @return: {*} |
|||
*/ |
|||
showNone(msg){ |
|||
uni.showToast({ |
|||
title: msg, |
|||
icon: 'none', |
|||
duration:2000 |
|||
}); |
|||
}, |
|||
|
|||
/** |
|||
* @description: 自动封装promise |
|||
* @param {Function} api |
|||
* @return {Promise API} |
|||
*/ |
|||
promisify(api){ |
|||
return (options, ...params) => { |
|||
return new Promise((resolve, reject) => { |
|||
api(Object.assign({}, options, { success: resolve, fail: reject }), ...params); |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* @description: 获取URL拼接 |
|||
* @param {JSON Object} data |
|||
* @param {String} url |
|||
* @return {String} |
|||
*/ |
|||
getWebURL(data,url){ |
|||
let result = "" |
|||
for(var i in data){ |
|||
result+=`&${i}=${data[i]}` |
|||
} |
|||
return url+"?"+result.slice(1) |
|||
}, |
|||
|
|||
/** |
|||
* @description: 休眠指定时间 |
|||
* @param {Int} time |
|||
* @return: {*} |
|||
*/ |
|||
async sleep(time){ |
|||
const res = await new Promise(resolve => { |
|||
setTimeout(() => resolve("asyncSetTimeOut"), time||1000); |
|||
}); |
|||
return res |
|||
}, |
|||
|
|||
/** |
|||
* @description: 解析onload传递参数,兼容多种传参方式 |
|||
* @param {String ,JSON Object } param |
|||
* @return: {*} |
|||
*/ |
|||
getDecodeObj(param) { //解析传递参数 - 兼容开发者工具快速调试参数,并做promise封装
|
|||
return new Promise((rs, rj) => { |
|||
let res = decodeURIComponent(param) |
|||
try { |
|||
res = JSON.parse(res) |
|||
} catch (e) { |
|||
//TODO handle the exception
|
|||
res = JSON.parse(decodeURIComponent(res)) || res |
|||
} finally { |
|||
if (typeof(res) == 'object') rs(res) |
|||
rj(res) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* @description: 防抖函数封装 |
|||
* @param {Function} func |
|||
* @param {Int} wait |
|||
* @param {Bool} immediate |
|||
* @return {debounce Function} |
|||
*/ |
|||
debounce(func, wait, immediate) { |
|||
let timeout, args, context, timestamp, result; |
|||
const later = function() { |
|||
// 据上一次触发时间间隔
|
|||
const last = +new Date() - timestamp; |
|||
// 上次被包装函数被调用时间间隔last小于设定时间间隔wait
|
|||
if (last < wait && last > 0) { |
|||
timeout = setTimeout(later, wait - last); |
|||
} else { |
|||
timeout = null; |
|||
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
|
|||
if (!immediate) { |
|||
result = func.apply(context, args); |
|||
if (!timeout) context = args = null; |
|||
} |
|||
} |
|||
} |
|||
return function(...args) { |
|||
context = this; |
|||
timestamp = +new Date(); |
|||
const callNow = immediate && !timeout; |
|||
// 如果延时不存在,重新设定延时
|
|||
if (!timeout) timeout = setTimeout(later, wait); |
|||
if (callNow) { |
|||
result = func.apply(context, args); |
|||
context = args = null; |
|||
} |
|||
return result; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* @description: 路由跳转,uni版(未兼容路由) |
|||
* @param {String Path} url |
|||
* @param {String } type |
|||
* @return: {*} |
|||
*/ |
|||
routeTo(url,type){ |
|||
switch(type){ |
|||
case 'nT': uni.navigateTo({url}); |
|||
break |
|||
case 'rT': uni.redirectTo({url}); |
|||
break |
|||
case 'rL': uni.reLaunch({url}); |
|||
break |
|||
case 'sT': uni.switchTab({url}); |
|||
break |
|||
default: uni.navigateBack({delta: 1}) |
|||
break |
|||
} |
|||
}, |
|||
|
|||
|
|||
} |
@ -0,0 +1,35 @@ |
|||
export default { |
|||
// #ifdef MP-WEIXIN
|
|||
// 小程序更新
|
|||
checkMiniProgramUpdate() { |
|||
if (uni.canIUse("getUpdateManager")) { |
|||
const updateManager = uni.getUpdateManager(); |
|||
updateManager.onCheckForUpdate(function(res) { |
|||
// 请求完新版本信息的回调
|
|||
if (res.hasUpdate) { |
|||
updateManager.onUpdateReady(function() { |
|||
uni.showModal({ |
|||
title: "更新提示", |
|||
content: "新版本已经准备好,是否重启应用?", |
|||
success: function(res) { |
|||
if (res.confirm) { |
|||
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
|
|||
updateManager.applyUpdate(); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
updateManager.onUpdateFailed(function() { |
|||
// 新的版本下载失败
|
|||
uni.showModal({ |
|||
title: "已经有新版本了哟~", |
|||
content: "新版本已经上线啦~,请您删除当前小程序,重新搜索打开哟~" |
|||
}); |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
// #endif
|
|||
|
|||
}; |
@ -0,0 +1,16 @@ |
|||
{ |
|||
"requires": true, |
|||
"lockfileVersion": 1, |
|||
"dependencies": { |
|||
"uni-read-pages": { |
|||
"version": "1.0.5", |
|||
"resolved": "https://registry.nlark.com/uni-read-pages/download/uni-read-pages-1.0.5.tgz", |
|||
"integrity": "sha1-RSyNyqiXe7rvYAkJvpJsjZcEOHw=" |
|||
}, |
|||
"uni-simple-router": { |
|||
"version": "2.0.7", |
|||
"resolved": "https://registry.npmmirror.com/uni-simple-router/download/uni-simple-router-2.0.7.tgz", |
|||
"integrity": "sha1-BOC1vmzXM6HsudNaPb6C8n9IIE4=" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"dependencies": { |
|||
"uni-read-pages": "^1.0.5", |
|||
"uni-simple-router": "^2.0.1" |
|||
} |
|||
} |
@ -0,0 +1,152 @@ |
|||
{ |
|||
"easycom": { //easycom,按需自动注册组件。原则上可以把所有页面引入组件方法删掉,会自动引入。 |
|||
"autoscan": true, |
|||
"custom": { |
|||
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue", |
|||
"^uni-(.*)": "@/components/uni-$1/uni-$1.vue", |
|||
"^nx-(.*)": "@/components/nx-$1/nx-$1.vue" |
|||
} |
|||
}, |
|||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages |
|||
{ |
|||
"path": "pages/index/index", |
|||
"aliasPath": "/", |
|||
"name":"index", |
|||
"style": { |
|||
"navigationBarTitleText": "赛事助手", |
|||
"navigationBarBackgroundColor":"#009874", |
|||
"navigationBarTextStyle":"white" |
|||
} |
|||
}, |
|||
{ |
|||
"path": "pages/message/center_list", |
|||
"name":"messageList", |
|||
"style": { |
|||
"navigationBarTitleText": "消息列表", |
|||
"navigationBarBackgroundColor":"#009874", |
|||
"navigationBarTextStyle":"white" |
|||
} |
|||
}, |
|||
|
|||
|
|||
{ |
|||
"path": "pages/message/detail", |
|||
"name":"messageDetail", |
|||
"style": { |
|||
"navigationBarTitleText": "消息详情", |
|||
"navigationBarBackgroundColor":"#009874", |
|||
"navigationBarTextStyle":"white" |
|||
} |
|||
}, |
|||
{ |
|||
"path": "pages/event/event_grade", |
|||
"name":"eventGrade", |
|||
"style": { |
|||
"navigationBarTitleText": "赛事打分", |
|||
"navigationBarBackgroundColor":"#009874", |
|||
"navigationBarTextStyle":"white" |
|||
} |
|||
}, |
|||
{ |
|||
"path": "pages/event/event_list", |
|||
"name":"eventList", |
|||
"style": { |
|||
"navigationBarTitleText": "赛事列表", |
|||
"navigationBarBackgroundColor":"#009874", |
|||
"navigationBarTextStyle":"white" |
|||
} |
|||
}, |
|||
{ |
|||
"path": "pages/login/reg", |
|||
"name":"reg", |
|||
"style": { |
|||
"navigationBarTitleText": "注册", |
|||
"navigationBarBackgroundColor":"#009874", |
|||
"navigationBarTextStyle":"white" |
|||
} |
|||
}, |
|||
{ |
|||
"path": "pages/login/login", |
|||
"name":"login", |
|||
"style": { |
|||
"navigationBarTitleText": "登录", |
|||
"navigationBarBackgroundColor":"#009874", |
|||
"navigationBarTextStyle":"white" |
|||
} |
|||
}, |
|||
{ |
|||
"path": "pages/login/forget", |
|||
"name":"forget", |
|||
"style": { |
|||
"navigationBarTitleText": "忘记密码", |
|||
"navigationBarBackgroundColor":"#009874", |
|||
"navigationBarTextStyle":"white" |
|||
} |
|||
}, |
|||
{ |
|||
"path": "pages/login/agreement", |
|||
"name":"agreement", |
|||
"style": { |
|||
"navigationBarTitleText": "用户协议", |
|||
"navigationBarBackgroundColor":"#009874", |
|||
"navigationBarTextStyle":"white" |
|||
} |
|||
}, |
|||
{ |
|||
"path": "pages/me/index", |
|||
"name":"me", |
|||
"style": { |
|||
"navigationBarTitleText": "我的", |
|||
"navigationBarBackgroundColor":"#009874", |
|||
"navigationBarTextStyle":"white" |
|||
} |
|||
} |
|||
], |
|||
"subPackages": [{ |
|||
"root": "pages/public", |
|||
"pages": [{ |
|||
"path": "404", |
|||
"name": "404", |
|||
"style": { |
|||
"navigationBarTitleText": "请重试" |
|||
} |
|||
}] |
|||
}], |
|||
|
|||
"globalStyle": { |
|||
"navigationBarTextStyle": "black", |
|||
"navigationBarTitleText": "通用模板", |
|||
"navigationBarBackgroundColor": "#F8F8F8", |
|||
"backgroundColor": "#F8F8F8" |
|||
}, |
|||
"tabBar": { |
|||
"color": "#7A7E83", |
|||
"selectedColor": "#fe519f", |
|||
"borderStyle": "black", |
|||
"backgroundColor": "#F8F8F8", |
|||
"list": [{ |
|||
"pagePath": "pages/index/index", |
|||
"iconPath": "static/images/tabbar/tab_home_01.png", |
|||
"selectedIconPath": "static/images/tabbar/tab_home_02.png", |
|||
"text": "首页" |
|||
}, |
|||
|
|||
{ |
|||
"pagePath": "pages/me/index", |
|||
"iconPath": "static/images/tabbar/tab_user_01.png", |
|||
"selectedIconPath": "static/images/tabbar/tab_user_02.png", |
|||
"text": "我的" |
|||
} |
|||
] |
|||
}, |
|||
"condition": { //模式配置,仅开发期间生效 |
|||
"current": 0, //当前激活的模式(list 的索引项) |
|||
"list": [ |
|||
{ |
|||
"name": "登录页面", //模式名称 |
|||
"path": "pages/login/login", //启动页面,必选 |
|||
"query": "" //启动参数,在页面的onLoad函数里面得到 |
|||
} |
|||
] |
|||
} |
|||
} |
@ -0,0 +1,327 @@ |
|||
<template> |
|||
|
|||
<view class="content flex_col flex_start"> |
|||
|
|||
<view class="head flex_row flex_around"> |
|||
<view class="tap-btn" :class="[getTabClass(e)]" @click="clickHead(i)" v-for="(e,i) in headDate" :key="i"> |
|||
{{e.name}} |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- item --> |
|||
<view class="item flex_col flex_start_y bg-white " v-for="i in 2"> |
|||
|
|||
<view class="title flex_row flex_start"> |
|||
<view class="text-m text-left">消息中心</view> |
|||
<!-- <view class="bandage"></view> --> |
|||
</view> |
|||
|
|||
<view class="info flex_row flex_between"> |
|||
<view class="time">2020.06.22</view> |
|||
<view class="type">半决赛</view> |
|||
</view> |
|||
|
|||
<view class="card flex_row flex_between"> |
|||
<view class="persion flex_row"> |
|||
<image class="bg-brown" src="../../static/images/event/event_lock.png" mode=""></image> |
|||
<text>adjksk</text> |
|||
</view> |
|||
|
|||
<view class="btn-box flex_row"> |
|||
<view class="btn state-0 flex_row">已打分</view> |
|||
<view class="btn state-1 flex_row" @click="openGrade()">打分</view> |
|||
<view class="btn state-2 flex_row" @click="openGradeMore()">数据</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 弹出交互 uview popup有bug,废弃重写--> |
|||
<!-- <u-popup :show="showGrade" @close="close" @open="open" mode="center" :closeOnClickOverlay="true" :closeable="true" :round="10"> |
|||
<view class="grade flex_col flex_start" > |
|||
<text class="g-title">赛事打分</text> |
|||
<text class="g-txt">李静分数录入</text> |
|||
<input type="text" value="" placeholder="请录入分数" /> |
|||
<view class="g-tip">请输入成员该赛程最终得分,非累计</view> |
|||
<view class="g-btn flex_row">保存</view> |
|||
</view> |
|||
</u-popup> --> |
|||
|
|||
<!-- 打分弹出 --> |
|||
<view class="overlay flex_col" v-if="showGrade"> |
|||
<view class="grade flex_col flex_start" > |
|||
<image class="g-close" src="../../static/images/event/event_close.png" mode="" @click="closeGrade()"></image> |
|||
<text class="g-title">赛事打分</text> |
|||
<text class="g-txt">李静分数录入</text> |
|||
<input type="text" value="" placeholder="请录入分数" placeholder-class="place-style"/> |
|||
<view class="g-tip">请输入成员该赛程最终得分,非累计</view> |
|||
<view class="g-btn flex_row" @click="closeGrade()">保存</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 赛事数据弹出 --> |
|||
<view class="overlay flex_col" v-if="showGradeMore"> |
|||
<view class="grade grade-more flex_col flex_start" > |
|||
<image class="g-close" src="../../static/images/event/event_close.png" mode="" @click="closeGradeMore()"></image> |
|||
<text class="g-title">入围赛数据</text> |
|||
<text class="g-txt">李静分数录入</text> |
|||
<view class="g-input-box"> |
|||
<view class="b-item flex_row"> |
|||
<text>篮板</text> |
|||
<input type="text" value="" placeholder="请录入数据" placeholder-class="place-style"/> |
|||
</view> |
|||
<view class="b-item flex_row"> |
|||
<text>助攻</text> |
|||
<input type="text" value="" placeholder="请录入数据" placeholder-class="place-style"/> |
|||
</view> |
|||
<view class="b-item flex_row"> |
|||
<text>场次</text> |
|||
<input type="text" value="" placeholder="请录入数据" placeholder-class="place-style"/> |
|||
</view> |
|||
|
|||
</view> |
|||
<view class="g-tip">请输入成员该赛程最终得分,非累计</view> |
|||
<view class="g-btn flex_row" @click="closeGradeMore()">保存</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- <u-modal :show="showGrade" @confirm="open" ref="uModal" :asyncClose="true" @close="close" :closeOnClickOverlay="true">确认????</u-modal> --> |
|||
</view> |
|||
</template> |
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
showGrade:false, |
|||
showGradeMore:true, |
|||
address:"", |
|||
headIndex:0, |
|||
headDate:[{name:'赛程打分'},{name:'赛事打分'}], |
|||
login:true |
|||
}; |
|||
}, |
|||
computed:{ |
|||
|
|||
}, |
|||
onLoad(parms) { |
|||
|
|||
}, |
|||
onUnload() { |
|||
|
|||
}, |
|||
methods: { |
|||
openGrade() { |
|||
this.showGrade = ! this.showGrade |
|||
console.log('open'); |
|||
}, |
|||
closeGrade() { |
|||
this.showGrade = false |
|||
console.log('close'); |
|||
}, |
|||
openGradeMore() { |
|||
this.showGradeMore = ! this.showGradeMore |
|||
console.log('open'); |
|||
}, |
|||
closeGradeMore() { |
|||
this.showGradeMore = false |
|||
console.log('close'); |
|||
}, |
|||
getTabClass(item){ |
|||
let {headDate,headIndex} = this |
|||
return headDate[headIndex].name==item.name?'active':'' |
|||
}, |
|||
clickHead(index){ |
|||
this.headIndex = index |
|||
}, |
|||
jumpPage(){ |
|||
this.$Router.push({name:"login"}) |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.content{ |
|||
|
|||
.head{ |
|||
width: 100%; |
|||
height: 112rpx; |
|||
background-color: white; |
|||
.tap-btn{ |
|||
font-size: 36rpx; |
|||
color: #333333; |
|||
font-weight: 700; |
|||
width: 200rpx; |
|||
height: 72rpx; |
|||
text-align: center; |
|||
line-height: 72rpx; |
|||
} |
|||
.active{ |
|||
color: white; |
|||
background: #009874; |
|||
border-radius: 36rpx; |
|||
} |
|||
} |
|||
|
|||
.item{ |
|||
color: #333333; |
|||
position: relative; |
|||
margin-top: 24rpx; |
|||
// border-radius: 10rpx; |
|||
width: 750rpx; |
|||
// height: 124rpx; |
|||
|
|||
.title{ |
|||
font-size: 36rpx; |
|||
color: #333333; |
|||
// margin-bottom: 10rpx; |
|||
font-weight: 700; |
|||
height: 116rpx; |
|||
width: 100%; |
|||
padding-left: 60rpx; |
|||
border-bottom: 1rpx solid #f2f2f7; |
|||
} |
|||
.info{ |
|||
font-weight: 700; |
|||
font-size: 32rpx; |
|||
color: #333333; |
|||
height: 96rpx; |
|||
width: 100%; |
|||
padding: 26rpx 24rpx 26rpx 60rpx; |
|||
border-bottom: 1rpx solid #f2f2f7; |
|||
} |
|||
.card{ |
|||
height: 188rpx; |
|||
width: 100%; |
|||
padding: 40rpx 24rpx 40rpx 50rpx; |
|||
border-bottom: 1rpx solid #f2f2f7; |
|||
.persion{ |
|||
> image{ |
|||
width: 108rpx; |
|||
height: 108rpx; |
|||
} |
|||
> text{ |
|||
font-size: 32rpx; |
|||
color: #333333; |
|||
margin-left: 24rpx; |
|||
} |
|||
} |
|||
|
|||
.btn-box{ |
|||
.btn{ |
|||
margin-left: 20rpx; |
|||
width: 132rpx; |
|||
height: 60rpx; |
|||
background: #FF873D; |
|||
border-radius: 6rpx; |
|||
color: white; |
|||
} |
|||
.state-0{ |
|||
background-color: #36C990; |
|||
} |
|||
.state-1{ |
|||
background-color: #009874; |
|||
} |
|||
.state-2{ |
|||
background-color: #FF873D; |
|||
} |
|||
} |
|||
} |
|||
.bandage{ |
|||
width: 16rpx; |
|||
height: 16rpx; |
|||
// position: absolute; |
|||
// right: 60rpx; |
|||
background: #EA5061; |
|||
border-radius: 50%; |
|||
font-size: 24rpx; |
|||
color: white; |
|||
text-align: center; |
|||
line-height: 34rpx; |
|||
margin-top: -16rpx; |
|||
margin-left: 10rpx; |
|||
} |
|||
} |
|||
} |
|||
.overlay{ |
|||
position: fixed; |
|||
height: 100vh; |
|||
width: 100%; |
|||
background: rgba(0,0,0,0.6); |
|||
z-index: 90; |
|||
.grade{ |
|||
z-index: 999; |
|||
width: 620rpx; |
|||
// height: 600rpx; |
|||
background: #FFFFFF; |
|||
padding: 50rpx; |
|||
border-radius: 10rpx; |
|||
.g-close{ |
|||
align-self: flex-end; |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
} |
|||
.g-title{ |
|||
margin-top: 20rpx; |
|||
font-size: 32rpx; |
|||
color: #333333; |
|||
font-weight: 700; |
|||
} |
|||
.g-txt{ |
|||
margin: 58rpx auto 12rpx auto; |
|||
font-size: 28rpx; |
|||
color: #333333; |
|||
} |
|||
input{ |
|||
width: 456rpx; |
|||
height: 88rpx; |
|||
background: #FFFFFF; |
|||
border: 2rpx solid #D8D8D8; |
|||
border-radius: 10rpx; |
|||
padding: 20rpx; |
|||
} |
|||
.g-tip{ |
|||
align-self: flex-start; |
|||
font-size: 24rpx; |
|||
color: #9A9A9D; |
|||
margin-top: 10rpx; |
|||
margin-bottom: 52rpx; |
|||
margin-left: 32rpx; |
|||
} |
|||
.g-btn{ |
|||
width: 240rpx; |
|||
height: 88rpx; |
|||
background: #009874; |
|||
border-radius: 10rpx; |
|||
font-size: 32rpx; |
|||
color: #FFFFFF; |
|||
font-weight: 700; |
|||
margin-bottom: 34rpx; |
|||
} |
|||
} |
|||
.grade-more{ |
|||
padding-left:72rpx; |
|||
padding-right: 80rpx; |
|||
.g-input-box{ |
|||
.b-item{ |
|||
margin-top: 20rpx; |
|||
> text{ |
|||
font-size: 28rpx; |
|||
color: #9C9C9F; |
|||
padding-right: 20rpx; |
|||
} |
|||
input { |
|||
width: 384rpx; |
|||
} |
|||
} |
|||
} |
|||
.g-tip{ |
|||
margin-left: 12rpx; |
|||
} |
|||
} |
|||
.place-style{ |
|||
font-size: 28rpx; |
|||
color: #9A9A9D; |
|||
} |
|||
} |
|||
|
|||
</style> |
@ -0,0 +1,196 @@ |
|||
<template> |
|||
<view class="content flex_col flex_start"> |
|||
|
|||
<!-- <view class="load-none flex_col"> |
|||
<image src="../../static/images/event/enent_none.png" mode="aspectFill"></image> |
|||
<text>还没有任何赛事~</text> |
|||
</view> --> |
|||
|
|||
<!-- item --> |
|||
<view class="item flex_col flex_start_y bg-white padding " v-for="i in 3" @click="jumpPage('eventGrade')"> |
|||
|
|||
<view class="time flex_row"> |
|||
<image src="../../static/images/event/event_lock.png" mode=""></image> |
|||
<text>2020.06.22</text> |
|||
<text>前报名</text> |
|||
</view> |
|||
<view class="box flex_row flex_start_y"> |
|||
|
|||
<image class="img" src="../../static/images/index/index_cup.png" mode=""></image> |
|||
|
|||
<view class="info flex_col flex_start flex_start_y"> |
|||
<view class="event_title flex_row flex_start flex_start_y"> |
|||
<text class="tag tag_green" >团队</text> |
|||
<text class="tag tag_blue" >个人</text> |
|||
<text class="">{{title|autoAddPoints}}</text> |
|||
</view> |
|||
<view class="location flex_row"> |
|||
<image src="../../static/images/event/event_location.png" mode=""></image> |
|||
<text class="text-maxline-one">{{address}}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<view class="load-finish">我是有底线的~</view> |
|||
|
|||
</view> |
|||
</template> |
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
title:"默认块级元素默认块级元素默认块级元素", |
|||
address:"广州省广州市区白云区广州省广州市区白云区永泰广州省广州市区白云区广州省广州市区白云区永泰", |
|||
login:true |
|||
}; |
|||
}, |
|||
onLoad(parms) { |
|||
|
|||
}, |
|||
onUnload() { |
|||
|
|||
}, |
|||
methods: { |
|||
jumpPage(name){ |
|||
this.$Router.push({name}) |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
page{ |
|||
background: #F2F2F7; |
|||
// height: 100vh; |
|||
} |
|||
.content{ |
|||
|
|||
.item{ |
|||
color: #333333; |
|||
position: relative; |
|||
margin-top: 24rpx; |
|||
border-radius: 10rpx; |
|||
width: 702rpx; |
|||
height: 368rpx; |
|||
.box{ |
|||
font-size: 32rpx; |
|||
color: #1A1A1A; |
|||
margin-bottom: 10rpx; |
|||
font-weight: 700; |
|||
.img{ |
|||
width: 300rpx; |
|||
height: 224rpx; |
|||
background-color: lightgray; |
|||
} |
|||
.info{ |
|||
width: 350rpx; |
|||
font-size: 36rpx; |
|||
color: #333333; |
|||
margin-left: 10rpx; |
|||
.location{ |
|||
// width: 330rpx; |
|||
margin-left: 10rpx; |
|||
margin-top: 14rpx; |
|||
font-size: 24rpx; |
|||
color: #9A9A9D; |
|||
// display: inline; |
|||
text-align: left; |
|||
>image{ |
|||
width: 28rpx; |
|||
height: 28rpx; |
|||
} |
|||
>text{ |
|||
margin-left: 5rpx; |
|||
width: 320rpx; |
|||
} |
|||
} |
|||
.event_title{ |
|||
text-align: left; |
|||
display: inline; |
|||
> text{ |
|||
margin-left: 10rpx; |
|||
} |
|||
} |
|||
.tag{ |
|||
// margin-right: 10rpx; |
|||
font-size: 24rpx; |
|||
width: 64rpx; |
|||
height: 36rpx; |
|||
border-radius: 6rpx; |
|||
text-align: center; |
|||
line-height: 36rpx; |
|||
padding: 5rpx 5rpx; |
|||
// margin: 10rpx 0; |
|||
|
|||
} |
|||
.tag_blue{ |
|||
color: #32C5FF; |
|||
background: #E0F6FF; |
|||
} |
|||
.tag_green{ |
|||
color: #36C990; |
|||
background: #DBF5EB; |
|||
} |
|||
} |
|||
} |
|||
.time{ |
|||
font-size: 28rpx; |
|||
color: #9A9A9D; |
|||
margin-bottom: 32rpx; |
|||
>image{ |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
} |
|||
>text{ |
|||
margin: 0 30rpx 0 10rpx; |
|||
} |
|||
|
|||
|
|||
} |
|||
.bandage{ |
|||
width: 16rpx; |
|||
height: 16rpx; |
|||
// position: absolute; |
|||
// right: 60rpx; |
|||
background: #EA5061; |
|||
border-radius: 50%; |
|||
font-size: 24rpx; |
|||
color: white; |
|||
text-align: center; |
|||
line-height: 34rpx; |
|||
margin-top: -16rpx; |
|||
margin-left: 10rpx; |
|||
} |
|||
} |
|||
|
|||
.load-finish{ |
|||
padding: 40rpx; |
|||
font-size: 28rpx; |
|||
color: #9A9A9D; |
|||
} |
|||
} |
|||
|
|||
.text-maxline-two { |
|||
text-overflow: ellipsis; |
|||
-webkit-line-clamp: 2; |
|||
-webkit-box-orient: vertical; |
|||
word-break: break-all; |
|||
overflow: hidden; |
|||
display: -webkit-box; |
|||
} |
|||
.load-none{ |
|||
> image{ |
|||
margin-top: 33vh; |
|||
width: 368rpx; |
|||
height: 316rpx; |
|||
} |
|||
> text{ |
|||
padding: 40rpx; |
|||
font-size: 28rpx; |
|||
color: #9A9A9D; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,118 @@ |
|||
<template> |
|||
<view class="content"> |
|||
|
|||
<view class="login-box flex_col flex_start"> |
|||
<image class="bg" src="../../static/images/index/index_bg.png" mode="aspectFit"></image> |
|||
|
|||
<view class="login-info flex_col" v-if="login"> |
|||
<text>1888888888</text> |
|||
<text>您好!</text> |
|||
</view> |
|||
<view class="button-login " v-else @click="jumpPage('login')">前往登录</view> |
|||
</view> |
|||
|
|||
<view class="item flex_row flex_start bg-white padding solid-bottom" @click="jumpPage('messageList')"> |
|||
<image src="../../static/images/index/index_bell.png" mode=""></image> |
|||
<view class="text-m text-left">消息中心</view> |
|||
<view class="bandage">99</view> |
|||
<image src="../../static/images/index/arrow_right.png" mode=""></image> |
|||
</view> |
|||
|
|||
<view class="item flex_row flex_start bg-white padding solid-bottom" @click="jumpPage('eventList')"> |
|||
<image src="../../static/images/index/index_cup.png" mode=""></image> |
|||
<view class="text-m text-left">活动赛事</view> |
|||
<image src="../../static/images/index/arrow_right.png" mode=""></image> |
|||
</view> |
|||
|
|||
<view class="item flex_row flex_start bg-white padding solid-bottom" v-if="login" @click="login=!login"> |
|||
<image src="../../static/images/index/index_logout.png" mode=""></image> |
|||
<view class="text-m text-left" >退出登录</view> |
|||
<image src="../../static/images/index/arrow_right.png" mode=""></image> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
address:"", |
|||
login:true |
|||
}; |
|||
}, |
|||
onLoad(parms) { |
|||
|
|||
}, |
|||
onUnload() { |
|||
|
|||
}, |
|||
methods: { |
|||
jumpPage(name){ |
|||
this.$Router.push({name:name}) |
|||
// this.$Router.pushTab('/pages/login/login') |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.content{ |
|||
.login-box{ |
|||
position: relative; |
|||
width: 750rpx; |
|||
height: 480rpx; |
|||
> image ,.bg{ |
|||
width: 100%;height: 100%; |
|||
position: absolute; |
|||
z-index: 1; |
|||
} |
|||
.button-login{ |
|||
margin-top: 132rpx; |
|||
color: #009874; |
|||
z-index: 20; |
|||
width: 240rpx; |
|||
height: 96rpx; |
|||
background: #FFFFFF; |
|||
border-radius: 48rpx; |
|||
text-align: center; |
|||
line-height: 96rpx; |
|||
font-weight: 900; |
|||
font-size: 36rpx; |
|||
letter-spacing: -0.2rpx; |
|||
} |
|||
.login-info{ |
|||
margin-top: 132rpx; |
|||
z-index: 20; |
|||
font-size: 40rpx; |
|||
color: #FFFFFF; |
|||
font-weight: 900; |
|||
} |
|||
} |
|||
.item{ |
|||
color: #333333; |
|||
position: relative; |
|||
> image:first-child{ |
|||
width: 52rpx; |
|||
height: 52rpx; |
|||
margin-right: 14rpx; |
|||
} |
|||
> image:last-child{ |
|||
width: 30rpx; |
|||
height: 30rpx; |
|||
position: absolute; |
|||
right: 20rpx; |
|||
} |
|||
.bandage{ |
|||
width: 34rpx; |
|||
height: 34rpx; |
|||
position: absolute; |
|||
right: 60rpx; |
|||
background: #EA5061; |
|||
border-radius: 50%; |
|||
font-size: 24rpx; |
|||
color: white; |
|||
text-align: center; |
|||
line-height: 34rpx; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,27 @@ |
|||
<template> |
|||
<view> |
|||
<web-view :webview-styles="webviewStyles" :src="articleurl"></web-view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
articleurl: '', |
|||
webviewStyles: { |
|||
progress: { |
|||
color: '#FF7200' |
|||
} |
|||
} |
|||
}; |
|||
}, |
|||
onLoad(options) { |
|||
this.articleurl = 'http://www.baidu.com/'; |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
@ -0,0 +1,255 @@ |
|||
<template> |
|||
<view class="content"> |
|||
<view class="list"> |
|||
<view class="tishi">若您忘记了密码,可在此重新设置新密码。</view> |
|||
<view class="list-call"> |
|||
<u-icon name="phone" color="#EE2626" size="40"></u-icon> |
|||
<input class="sl-input" type="number" v-model="phone" maxlength="11" placeholder="请输入手机号" /> |
|||
</view> |
|||
<view class="list-call"> |
|||
<u-icon name="lock" color="#EE2626" size="40"></u-icon> |
|||
<input class="sl-input" type="text" v-model="password" maxlength="32" placeholder="请输入新密码" |
|||
:password="!showPassword" /> |
|||
<u-icon @click="display" :name="showPassword?'eye-off':'eye-fill'" color="#EE2626" size="40"></u-icon> |
|||
|
|||
</view> |
|||
<view class="list-call"> |
|||
<u-icon name="checkmark-circle" color="#EE2626" size="40"></u-icon> |
|||
<input class="sl-input" type="number" v-model="code" maxlength="4" placeholder="验证码" /> |
|||
<view class="yzm" :class="{ yzms: second>0 }" @tap="getcode">{{yanzhengma}}</view> |
|||
</view> |
|||
</view> |
|||
<view class="button-login" hover-class="button-hover" @tap="bindLogin()"> |
|||
<text>修改密码</text> |
|||
</view> |
|||
|
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
var _this, js; |
|||
export default { |
|||
data() { |
|||
return { |
|||
phone: '', |
|||
second: 0, |
|||
code: "", |
|||
showPassword: false, |
|||
password: '' |
|||
} |
|||
}, |
|||
onLoad() { |
|||
_this = this; |
|||
}, |
|||
computed: { |
|||
yanzhengma() { |
|||
if (this.second == 0) { |
|||
return '获取验证码'; |
|||
} else { |
|||
if (this.second < 10) { |
|||
return '重新获取0' + this.second; |
|||
} else { |
|||
return '重新获取' + this.second; |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
onUnload() { |
|||
this.clear() |
|||
}, |
|||
methods: { |
|||
display() { |
|||
this.showPassword = !this.showPassword |
|||
}, |
|||
clear() { |
|||
clearInterval(js) |
|||
js = null |
|||
this.second = 0 |
|||
}, |
|||
getcode() { |
|||
if (this.phone.length != 11) { |
|||
uni.showToast({ |
|||
icon: 'none', |
|||
title: '手机号不正确' |
|||
}); |
|||
return; |
|||
} |
|||
if (this.second > 0) { |
|||
return; |
|||
} |
|||
_this.second = 60; |
|||
js = setInterval(function() { |
|||
_this.second--; |
|||
if (_this.second == 0) { |
|||
_this.clear() |
|||
} |
|||
}, 1000) |
|||
uni.request({ |
|||
url: 'http://***/getPassWord', //仅为示例,并非真实接口地址。 |
|||
data: { |
|||
phone: this.phone, |
|||
type: 'forget' |
|||
}, |
|||
method: 'POST', |
|||
dataType: 'json', |
|||
success: (res) => { |
|||
if (res.data.code != 200) { |
|||
uni.showToast({ |
|||
title: res.data.msg, |
|||
icon: 'none' |
|||
}); |
|||
_this.second = 0; |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.data.msg |
|||
}); |
|||
_this.second = 60; |
|||
js = setInterval(function() { |
|||
_this.second--; |
|||
if (_this.second == 0) { |
|||
_this.clear() |
|||
} |
|||
}, 1000) |
|||
} |
|||
}, |
|||
fail() { |
|||
this.clear() |
|||
} |
|||
}); |
|||
|
|||
|
|||
|
|||
|
|||
}, |
|||
bindLogin() { |
|||
if (this.phone.length != 11) { |
|||
uni.showToast({ |
|||
icon: 'none', |
|||
title: '手机号不正确' |
|||
}); |
|||
return; |
|||
} |
|||
if (this.password.length < 6) { |
|||
uni.showToast({ |
|||
icon: 'none', |
|||
title: '密码不正确' |
|||
}); |
|||
return; |
|||
} |
|||
if (this.code.length != 4) { |
|||
uni.showToast({ |
|||
icon: 'none', |
|||
title: '验证码不正确' |
|||
}); |
|||
return; |
|||
} |
|||
uni.request({ |
|||
url: 'http://***/forget', |
|||
data: { |
|||
phone: this.phone, |
|||
password: this.password, |
|||
code: this.code |
|||
}, |
|||
method: 'POST', |
|||
dataType: 'json', |
|||
success: (res) => { |
|||
if (res.data.code != 200) { |
|||
uni.showToast({ |
|||
title: res.data.msg, |
|||
icon: 'none' |
|||
}); |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.data.msg |
|||
}); |
|||
setTimeout(function() { |
|||
uni.navigateBack(); |
|||
}, 1500) |
|||
} |
|||
} |
|||
}); |
|||
|
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
page { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.tishi { |
|||
color: #999999; |
|||
font-size: 28rpx; |
|||
line-height: 50rpx; |
|||
margin-bottom: 50rpx; |
|||
} |
|||
|
|||
.list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding-top: 50rpx; |
|||
padding-left: 70rpx; |
|||
padding-right: 70rpx; |
|||
} |
|||
|
|||
.list-call { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
height: 100rpx; |
|||
color: #333333; |
|||
border-bottom: 0.5px solid #e2e2e2; |
|||
} |
|||
|
|||
|
|||
.list-call .sl-input { |
|||
flex: 1; |
|||
text-align: left; |
|||
font-size: 32rpx; |
|||
margin-left: 16rpx; |
|||
} |
|||
|
|||
.button-login { |
|||
color: #FFFFFF; |
|||
font-size: 34rpx; |
|||
width: 470rpx; |
|||
height: 100rpx; |
|||
background: linear-gradient(-90deg, rgba(193, 25, 32, 1), rgba(238, 38, 38, 1)); |
|||
border-radius: 50rpx; |
|||
line-height: 100rpx; |
|||
text-align: center; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
margin-top: 100rpx; |
|||
} |
|||
|
|||
.button-hover { |
|||
background: linear-gradient(-90deg, rgba(193, 25, 32, 0.8), rgba(238, 38, 38, 0.8)); |
|||
} |
|||
|
|||
.yzm { |
|||
color: #FF7D13; |
|||
font-size: 24rpx; |
|||
line-height: 64rpx; |
|||
padding-left: 10rpx; |
|||
padding-right: 10rpx; |
|||
width: auto; |
|||
height: 64rpx; |
|||
border: 1rpx solid rgba(255, 131, 30, 1); |
|||
border-radius: 50rpx; |
|||
} |
|||
|
|||
.yzms { |
|||
color: #999999 !important; |
|||
border: 1rpx solid #999999; |
|||
} |
|||
</style> |
@ -0,0 +1,242 @@ |
|||
<template> |
|||
<view class="content"> |
|||
<view class="header flex_row"> |
|||
<image src="/static/images/logo_main.png"></image> |
|||
</view> |
|||
|
|||
<view class="list flex_col"> |
|||
<view class="sjh list-call"> |
|||
<input class="sl-input" v-model="login.mobile" type="number" maxlength="11" placeholder="输入手机号码" /> |
|||
</view> |
|||
<view class="tips">手机号码格式错误,请重新填写</view> |
|||
|
|||
<view class="yzm flex_row"> |
|||
<view class="list-call l-short"> |
|||
<input class="sl-input" v-model="login.verify_code" type="text" maxlength="6" placeholder="输入验证码" |
|||
password="true" /> |
|||
</view> |
|||
|
|||
<view class="list-call l-small flex_col" @click="sendSMS"> |
|||
获取验证码 |
|||
</view> |
|||
</view> |
|||
<view class="tips">验证码错误,请重新填写</view> |
|||
</view> |
|||
|
|||
|
|||
<view class="agreement-box flex_row flex_start"> |
|||
<image @click="agreed=!agreed" class="a-icon" src="../../static/images/login/agreed_circle.png" mode="aspectFit" v-if="agreed"></image> |
|||
<image @click="agreed=!agreed" class="a-icon" src="../../static/images/login/agree_circle.png" mode="aspectFit" v-else></image> |
|||
我已同意<text @click="jumpAgreement()">《隐私政策》</text>及<text @click="jumpAgreement()">《用户注册服务协议》</text> |
|||
</view> |
|||
|
|||
<view class="button-login" hover-class="button-hover" @tap="bindLogin()"> |
|||
<text>登录</text> |
|||
</view> |
|||
<button class="button-login button-reverse" hover-class="button-hover" open-type="getPhoneNumber" @getphonenumber="decryptPhoneNumber" > |
|||
<text >获取微信手机号登录</text> |
|||
</button> |
|||
|
|||
<!-- <view class="agreenment"> |
|||
<navigator url="/pages/login/forget" open-type="navigate">忘记密码</navigator> |
|||
<text>|</text> |
|||
<navigator url="/pages/login/reg" open-type="navigate">注册账户</navigator> |
|||
</view> --> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
//没有挂载全局而按需引用接口,可以更清楚知道当前page所用接口 |
|||
import { |
|||
postLogin,postSendSMS,wechatGetPhoneNumber |
|||
} from "@/nxTemp/apis/userAPI.js" |
|||
export default { |
|||
data() { |
|||
return { |
|||
agreed:false, |
|||
login: {//后台让先写死 |
|||
mobile: '13500071371',//手机 |
|||
ticket: 'test',//短信验证码返回的 |
|||
verify_code:'666666',//验证码 |
|||
}, |
|||
WXdetail:{ |
|||
code:"", |
|||
appid:"", |
|||
encryptedData:"", |
|||
iv:"", |
|||
} |
|||
|
|||
}; |
|||
}, |
|||
methods: { |
|||
bindLogin() { |
|||
// let that = this; |
|||
if (this.login.mobile.length != 11) { |
|||
return this.$tools.showNone("请输入正确的手机号");} |
|||
if (this.login.verify_code.length < 6) { |
|||
return this.$tools.showNone("请输入正确的验证码");} |
|||
postLogin(this.login).then(res => { |
|||
this.$tools.showNone("登录成功"); |
|||
this.$store.dispatch('setUserData', res); |
|||
this.$Router.pushTab({// 跳转首页 |
|||
path: '/pages/index/index', |
|||
}); |
|||
// console.log(this.$store); |
|||
}); |
|||
}, |
|||
jumpAgreement(){ |
|||
this.$Router.push('/pages/login/agreement') |
|||
}, |
|||
sendSMS(){ |
|||
let {mobile} = this.login |
|||
postSendSMS({mobile}).then(res => { |
|||
this.$tools.showNone("发送成功"); |
|||
this.login.ticket = res.data||"" |
|||
}); |
|||
}, |
|||
|
|||
async decryptPhoneNumber(e){ |
|||
if(e.detail.errMsg!=="getPhoneNumber:ok")return this.$tools.showNone(e.detail.errMsg) |
|||
this.$tools.showNone("解析数据...") |
|||
|
|||
const accountInfo = uni.getAccountInfoSync(); |
|||
let {encryptedData,iv} = e.detail |
|||
let {code} = await this.$tools.promisify(uni.login)() |
|||
let appid = accountInfo.miniProgram.appId |
|||
this.WXdetail = {encryptedData,iv,code,appid} |
|||
this.getWXPhoneNumber(this.WXdetail) |
|||
}, |
|||
getWXPhoneNumber(WXdetail){ |
|||
wechatGetPhoneNumber(WXdetail).then(res => { |
|||
this.$tools.showNone("发送成功") |
|||
this.login.ticket = res.data||"" |
|||
}); |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
page { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.header { |
|||
width: 261rpx; |
|||
height: 261rpx; |
|||
border-radius: 50%; |
|||
margin-top: 130rpx; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
background-color: #009874; |
|||
} |
|||
|
|||
.header image { |
|||
width: 171rpx; |
|||
height: 171rpx; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding-top: 130rpx; |
|||
padding-left: 70rpx; |
|||
padding-right: 70rpx; |
|||
position: relative; |
|||
.tips{ |
|||
// position: absolute; |
|||
// left: 10rpx; |
|||
text-align: left; |
|||
width: 100%; |
|||
margin-bottom: 20rpx; |
|||
margin-top: 10rpx; |
|||
color: #EA5061; |
|||
margin-left: -20rpx; |
|||
} |
|||
} |
|||
|
|||
.list-call { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
height: 100rpx; |
|||
width: 688rpx; |
|||
color: #333333; |
|||
background: #F2F2F7; |
|||
border-radius: 10rpx; |
|||
// margin-bottom: 60rpx; |
|||
/* border-bottom: 0.5px solid #e2e2e2; */ |
|||
|
|||
} |
|||
.l-short{ |
|||
width: 428rpx; |
|||
} |
|||
.l-small{ |
|||
width: 240rpx; |
|||
margin-left: 20rpx; |
|||
color: #009874; |
|||
justify-content: center; |
|||
} |
|||
|
|||
|
|||
.list-call .sl-input { |
|||
flex: 1; |
|||
text-align: left; |
|||
font-size: 32rpx; |
|||
margin-left: 16rpx; |
|||
} |
|||
|
|||
.button-login { |
|||
color: #FFFFFF; |
|||
font-size: 34rpx; |
|||
width: 688rpx; |
|||
height: 100rpx; |
|||
border-radius: 10rpx; |
|||
background: #009874; |
|||
line-height: 100rpx; |
|||
text-align: center; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
margin-top: 40rpx; |
|||
font-weight: 800; |
|||
> button{ |
|||
width: 688rpx; |
|||
height: 100%; |
|||
// height: 100rpx; |
|||
flex-shrink: 0; |
|||
padding: 0; |
|||
border: none; |
|||
font-size: 34rpx; |
|||
} |
|||
} |
|||
.button-reverse{ |
|||
color: #009874; |
|||
background: white; |
|||
border: 2rpx solid #009874; |
|||
} |
|||
|
|||
.button-hover { |
|||
// background: linear-gradient(-90deg, rgba(0, 186, 30, 0.8), rgba(0, 231, 0, 0.8)); |
|||
} |
|||
|
|||
.agreement-box{ |
|||
padding-left: 30rpx; |
|||
color: #696D6F; |
|||
.a-icon{ |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
margin-right: 20rpx; |
|||
} |
|||
> text{ |
|||
color: #009874; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,303 @@ |
|||
<template> |
|||
<view class="content"> |
|||
<view class="header"> |
|||
<image src="/static/images/login/logo.jpg"></image> |
|||
</view> |
|||
|
|||
<view class="list"> |
|||
<view class="list-call"> |
|||
<u-icon name="phone" color="#EE2626" size="40"></u-icon> |
|||
<input class="sl-input" v-model="phone" type="number" maxlength="11" placeholder="手机号" /> |
|||
</view> |
|||
<view class="list-call"> |
|||
<u-icon name="lock" color="#EE2626" size="40"></u-icon> |
|||
<input class="sl-input" v-model="password" type="text" maxlength="32" placeholder="登录密码" |
|||
:password="!showPassword" /> |
|||
<u-icon @click="display" :name="showPassword?'eye-off':'eye-fill'" color="#EE2626" size="40"></u-icon> |
|||
</view> |
|||
<view class="list-call"> |
|||
<u-icon name="checkmark-circle" color="#EE2626" size="40"></u-icon> |
|||
<input class="sl-input" v-model="code" type="number" maxlength="4" placeholder="验证码" /> |
|||
<view class="yzm" :class="{ yzms: second>0 }" @tap="getcode">{{yanzhengma}}</view> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<view class="button-login" hover-class="button-hover" @tap="bindLogin"> |
|||
<text>注册</text> |
|||
</view> |
|||
|
|||
<view class="agreement"> |
|||
<image @tap="agreementSuccess" |
|||
:src="agreement==true?'/static/images/login/ty1.png':'/static/images/login/ty0.png'"></image> |
|||
<text @tap="agreementSuccess"> 同意</text> |
|||
<navigator url="/pages/login/agreement?id=1" open-type="navigate">《软件用户协议》</navigator> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
var _this, js; |
|||
export default { |
|||
onLoad() { |
|||
_this = this; |
|||
|
|||
}, |
|||
onUnload() { |
|||
clearInterval(js) |
|||
this.second = 0; |
|||
}, |
|||
data() { |
|||
return { |
|||
phone: '', |
|||
password: '', |
|||
code: '', |
|||
agreement: true, |
|||
showPassword: false, |
|||
second: 0 |
|||
}; |
|||
}, |
|||
computed: { |
|||
yanzhengma() { |
|||
if (this.second == 0) { |
|||
return '获取验证码'; |
|||
} else { |
|||
if (this.second < 10) { |
|||
return '重新获取0' + this.second; |
|||
} else { |
|||
return '重新获取' + this.second; |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
onUnload() { |
|||
this.clear() |
|||
}, |
|||
methods: { |
|||
clear() { |
|||
clearInterval(js) |
|||
js = null |
|||
this.second = 0 |
|||
}, |
|||
display() { |
|||
this.showPassword = !this.showPassword |
|||
}, |
|||
agreementSuccess() { |
|||
this.agreement = !this.agreement; |
|||
}, |
|||
getcode() { |
|||
if (this.phone.length != 11) { |
|||
uni.showToast({ |
|||
icon: 'none', |
|||
title: '手机号不正确' |
|||
}); |
|||
return; |
|||
} |
|||
if (this.second > 0) { |
|||
return; |
|||
} |
|||
this.second = 60; |
|||
//请求业务 |
|||
js = setInterval(function() { |
|||
_this.second--; |
|||
if (_this.second == 0) { |
|||
_this.clear() |
|||
} |
|||
}, 1000) |
|||
// uni.request({ |
|||
// url: 'http://***/getcode.html', //仅为示例,并非真实接口地址。 |
|||
// data: { |
|||
// phone: this.phone, |
|||
// type: 'reg' |
|||
// }, |
|||
// method: 'POST', |
|||
// dataType: 'json', |
|||
// success: (res) => { |
|||
// if (res.data.code != 200) { |
|||
// uni.showToast({ |
|||
// title: res.data.msg, |
|||
// icon: 'none' |
|||
// }); |
|||
// } else { |
|||
// uni.showToast({ |
|||
// title: res.data.msg |
|||
// }); |
|||
// js = setInterval(function() { |
|||
// _this.second--; |
|||
// if (_this.second == 0) { |
|||
// _this.clear() |
|||
// } |
|||
// }, 1000) |
|||
// } |
|||
// }, |
|||
// fail() { |
|||
// this.second == 0 |
|||
// } |
|||
// }); |
|||
}, |
|||
bindLogin() { |
|||
if (this.agreement == false) { |
|||
uni.showToast({ |
|||
icon: 'none', |
|||
title: '请先阅读《软件用户协议》' |
|||
}); |
|||
return; |
|||
} |
|||
if (this.phone.length != 11) { |
|||
uni.showToast({ |
|||
icon: 'none', |
|||
title: '手机号不正确' |
|||
}); |
|||
return; |
|||
} |
|||
if (this.password.length < 6) { |
|||
uni.showToast({ |
|||
icon: 'none', |
|||
title: '密码不正确' |
|||
}); |
|||
return; |
|||
} |
|||
if (this.code.length != 4) { |
|||
uni.showToast({ |
|||
icon: 'none', |
|||
title: '验证码不正确' |
|||
}); |
|||
return; |
|||
} |
|||
uni.request({ |
|||
url: 'http://***/reg.html', |
|||
data: { |
|||
phone: this.phone, |
|||
password: this.password, |
|||
code: this.code, |
|||
}, |
|||
method: 'POST', |
|||
dataType: 'json', |
|||
success: (res) => { |
|||
if (res.data.code != 200) { |
|||
uni.showToast({ |
|||
title: res.data.msg, |
|||
icon: 'none' |
|||
}); |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.data.msg |
|||
}); |
|||
setTimeout(function() { |
|||
uni.navigateBack(); |
|||
}, 1500) |
|||
} |
|||
} |
|||
}); |
|||
|
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
page { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.header { |
|||
width: 161rpx; |
|||
height: 161rpx; |
|||
border-radius: 50%; |
|||
margin-top: 30rpx; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
} |
|||
|
|||
.header image { |
|||
width: 161rpx; |
|||
height: 161rpx; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding-top: 50rpx; |
|||
padding-left: 70rpx; |
|||
padding-right: 70rpx; |
|||
} |
|||
|
|||
.list-call { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
height: 100rpx; |
|||
color: #333333; |
|||
border-bottom: 0.5px solid #e2e2e2; |
|||
} |
|||
|
|||
|
|||
|
|||
.list-call .sl-input { |
|||
flex: 1; |
|||
text-align: left; |
|||
font-size: 32rpx; |
|||
margin-left: 16rpx; |
|||
} |
|||
|
|||
.yzm { |
|||
color: #FF7D13; |
|||
font-size: 24rpx; |
|||
line-height: 64rpx; |
|||
padding-left: 10rpx; |
|||
padding-right: 10rpx; |
|||
width: auto; |
|||
height: 64rpx; |
|||
border: 1rpx solid #FFA800; |
|||
border-radius: 50rpx; |
|||
} |
|||
|
|||
.yzms { |
|||
color: #999999 !important; |
|||
border: 1rpx solid #999999; |
|||
} |
|||
|
|||
.button-login { |
|||
color: #FFFFFF; |
|||
font-size: 34rpx; |
|||
width: 470rpx; |
|||
height: 100rpx; |
|||
line-height: 100rpx; |
|||
background: linear-gradient(-90deg, rgba(193, 25, 32, 1), rgba(238, 38, 38, 1)); |
|||
border-radius: 50rpx; |
|||
text-align: center; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
margin-top: 100rpx; |
|||
} |
|||
|
|||
.button-hover { |
|||
background: linear-gradient(-90deg, rgba(193, 25, 32, 0.8), rgba(238, 38, 38, 0.8)); |
|||
} |
|||
|
|||
.agreement { |
|||
display: flex; |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
font-size: 30rpx; |
|||
margin-top: 80rpx; |
|||
color: #FFA800; |
|||
text-align: center; |
|||
height: 40rpx; |
|||
line-height: 40rpx; |
|||
} |
|||
|
|||
.agreement image { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
} |
|||
</style> |
@ -0,0 +1,30 @@ |
|||
<template> |
|||
<view> |
|||
<view class="bg-white padding"> |
|||
<view class="text-xl text-center">我的 |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
<script> |
|||
export default { |
|||
components: {}, |
|||
data() { |
|||
return { |
|||
|
|||
}; |
|||
}, |
|||
onShow() { |
|||
//加载 |
|||
}, |
|||
onLoad() { |
|||
|
|||
}, |
|||
methods: { |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
|
|||
</style> |
@ -0,0 +1,74 @@ |
|||
<template> |
|||
<view class="content flex_col flex_start"> |
|||
|
|||
<!-- item --> |
|||
<view class="item flex_col flex_start_y bg-white padding " v-for="i in 3"> |
|||
<view class="title flex_row flex_start"> |
|||
<view class="text-m text-left">消息中心</view> |
|||
<view class="bandage"></view> |
|||
</view> |
|||
<view class="time">2020.06.22</view> |
|||
</view> |
|||
|
|||
|
|||
</view> |
|||
</template> |
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
address:"", |
|||
login:true |
|||
}; |
|||
}, |
|||
onLoad(parms) { |
|||
|
|||
}, |
|||
onUnload() { |
|||
|
|||
}, |
|||
methods: { |
|||
jumpPage(){ |
|||
this.$Router.push({name:"login"}) |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.content{ |
|||
|
|||
.item{ |
|||
color: #333333; |
|||
position: relative; |
|||
margin-top: 24rpx; |
|||
border-radius: 10rpx; |
|||
width: 702rpx; |
|||
height: 124rpx; |
|||
.title{ |
|||
font-size: 32rpx; |
|||
color: #1A1A1A; |
|||
margin-bottom: 10rpx; |
|||
font-weight: 700; |
|||
} |
|||
.time{ |
|||
font-size: 24rpx; |
|||
color: #B2B2B2; |
|||
} |
|||
.bandage{ |
|||
width: 16rpx; |
|||
height: 16rpx; |
|||
// position: absolute; |
|||
// right: 60rpx; |
|||
background: #EA5061; |
|||
border-radius: 50%; |
|||
font-size: 24rpx; |
|||
color: white; |
|||
text-align: center; |
|||
line-height: 34rpx; |
|||
margin-top: -16rpx; |
|||
margin-left: 10rpx; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,69 @@ |
|||
<template> |
|||
<view class="content flex_col flex_start bg-white"> |
|||
|
|||
<!-- title --> |
|||
<view class="title_item flex_col flex_start_y bg-white padding " > |
|||
<view class="title flex_row flex_start"> |
|||
<view class="text-m text-left">欧轩智能商家助手功能更新</view> |
|||
<view class="bandage"></view> |
|||
</view> |
|||
<view class="time">2020.06.22</view> |
|||
</view> |
|||
|
|||
<rich-text class="txt" :nodes="txt"></rich-text> |
|||
</view> |
|||
</template> |
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
txt:"欧轩智能商家助手将于2020.09.16上线!欧轩智能商家助手将于2020.09.16上线!欧轩智能商家助手将于2020.09.16上线!欧轩智能商家助手将于上线欧轩智能商家助手将于上线欧轩智能商家助手将于2020.09.16上线!欧轩智能商家助手将于欧轩智能商家助手将于上线欧轩智能商家助手将于2020.09.16上线欧轩智能商家助手将于2020.09.16上线!", |
|||
login:true |
|||
}; |
|||
}, |
|||
onLoad(parms) { |
|||
|
|||
}, |
|||
onUnload() { |
|||
|
|||
}, |
|||
methods: { |
|||
jumpPage(){ |
|||
this.$Router.push({name:"login"}) |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
page{ |
|||
background-color: white; |
|||
} |
|||
.content{ |
|||
|
|||
.title_item{ |
|||
color: #333333; |
|||
position: relative; |
|||
margin-top: 24rpx; |
|||
border-radius: 10rpx; |
|||
width: 100%; |
|||
margin-bottom: 60rpx; |
|||
// height: 124rpx; |
|||
.title{ |
|||
margin-bottom: 16rpx; |
|||
font-weight: 700; |
|||
font-size: 40rpx; |
|||
color: #1A1A1A |
|||
} |
|||
.time{ |
|||
font-size: 24rpx; |
|||
color: #B2B2B2; |
|||
} |
|||
|
|||
} |
|||
.txt{ |
|||
width: 662rpx; |
|||
padding-bottom:10rpx ; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,13 @@ |
|||
<template> |
|||
|
|||
|
|||
<u-empty mode="page"> |
|||
<u-button slot="bottom" size="medium" @click="$Router.pushTab('/pages/tabbar/home')"> |
|||
去首页 |
|||
</u-button> |
|||
</u-empty> |
|||
</template> |
|||
|
|||
<script></script> |
|||
|
|||
<style></style> |
@ -0,0 +1,184 @@ |
|||
/* |
|||
Animation 微动画 |
|||
基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28 |
|||
*/ |
|||
|
|||
/* css 滤镜 控制黑白底色gif的 */ |
|||
.gif-black{ |
|||
mix-blend-mode: screen; |
|||
} |
|||
.gif-white{ |
|||
mix-blend-mode: multiply; |
|||
} |
|||
|
|||
|
|||
/* Animation css */ |
|||
[class*=animation-] { |
|||
animation-duration: .5s; |
|||
animation-timing-function: ease-out; |
|||
animation-fill-mode: both |
|||
} |
|||
|
|||
.animation-fade { |
|||
animation-name: fade; |
|||
animation-duration: .8s; |
|||
animation-timing-function: linear |
|||
} |
|||
|
|||
.animation-scale-up { |
|||
animation-name: scale-up |
|||
} |
|||
|
|||
.animation-scale-down { |
|||
animation-name: scale-down |
|||
} |
|||
|
|||
.animation-slide-top { |
|||
animation-name: slide-top |
|||
} |
|||
|
|||
.animation-slide-bottom { |
|||
animation-name: slide-bottom |
|||
} |
|||
|
|||
.animation-slide-left { |
|||
animation-name: slide-left |
|||
} |
|||
|
|||
.animation-slide-right { |
|||
animation-name: slide-right |
|||
} |
|||
|
|||
.animation-shake { |
|||
animation-name: shake |
|||
} |
|||
|
|||
.animation-reverse { |
|||
animation-direction: reverse |
|||
} |
|||
|
|||
@keyframes fade { |
|||
0% { |
|||
opacity: 0 |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1 |
|||
} |
|||
} |
|||
|
|||
@keyframes scale-up { |
|||
0% { |
|||
opacity: 0; |
|||
transform: scale(.2) |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: scale(1) |
|||
} |
|||
} |
|||
|
|||
@keyframes scale-down { |
|||
0% { |
|||
opacity: 0; |
|||
transform: scale(1.8) |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: scale(1) |
|||
} |
|||
} |
|||
|
|||
@keyframes slide-top { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateY(-100%) |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateY(0) |
|||
} |
|||
} |
|||
|
|||
@keyframes slide-bottom { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateY(100%) |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateY(0) |
|||
} |
|||
} |
|||
|
|||
@keyframes shake { |
|||
|
|||
0%, |
|||
100% { |
|||
transform: translateX(0) |
|||
} |
|||
|
|||
10% { |
|||
transform: translateX(-9px) |
|||
} |
|||
|
|||
20% { |
|||
transform: translateX(8px) |
|||
} |
|||
|
|||
30% { |
|||
transform: translateX(-7px) |
|||
} |
|||
|
|||
40% { |
|||
transform: translateX(6px) |
|||
} |
|||
|
|||
50% { |
|||
transform: translateX(-5px) |
|||
} |
|||
|
|||
60% { |
|||
transform: translateX(4px) |
|||
} |
|||
|
|||
70% { |
|||
transform: translateX(-3px) |
|||
} |
|||
|
|||
80% { |
|||
transform: translateX(2px) |
|||
} |
|||
|
|||
90% { |
|||
transform: translateX(-1px) |
|||
} |
|||
} |
|||
|
|||
@keyframes slide-left { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateX(-100%) |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateX(0) |
|||
} |
|||
} |
|||
|
|||
@keyframes slide-right { |
|||
0% { |
|||
opacity: 0; |
|||
transform: translateX(100%) |
|||
} |
|||
|
|||
100% { |
|||
opacity: 1; |
|||
transform: translateX(0) |
|||
} |
|||
} |
@ -0,0 +1,69 @@ |
|||
<template> |
|||
<view> |
|||
<view class="cu-custom" :style="[{height:CustomBar + 'px'}]"> |
|||
<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]"> |
|||
<view class="action" @tap="BackPage" v-if="isBack"> |
|||
<text class="cuIcon-back"></text> |
|||
<slot name="backText"></slot> |
|||
</view> |
|||
<view class="content" :style="[{top:StatusBar + 'px'}]"> |
|||
<slot name="content"></slot> |
|||
</view> |
|||
<slot name="right"></slot> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
StatusBar: this.StatusBar, |
|||
CustomBar: this.CustomBar |
|||
}; |
|||
}, |
|||
name: 'cu-custom', |
|||
computed: { |
|||
style() { |
|||
var StatusBar= this.StatusBar; |
|||
var CustomBar= this.CustomBar; |
|||
var bgImage = this.bgImage; |
|||
var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`; |
|||
if (this.bgImage) { |
|||
style = `${style}background-image:url(${bgImage});`; |
|||
} |
|||
return style |
|||
} |
|||
}, |
|||
props: { |
|||
bgColor: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
isBack: { |
|||
type: [Boolean, String], |
|||
default: false |
|||
}, |
|||
bgImage: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
}, |
|||
methods: { |
|||
BackPage() { |
|||
if (getCurrentPages().length < 2 && 'undefined' !== typeof __wxConfig) { |
|||
let url = '/' + __wxConfig.pages[0] |
|||
return uni.redirectTo({url}) |
|||
} |
|||
uni.navigateBack({ |
|||
delta: 1 |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
1226
static/colorui/icon.css
File diff suppressed because it is too large
View File
3912
static/colorui/main.css
File diff suppressed because it is too large
View File
After Width: 736 | Height: 632 | Size: 27 KiB |
After Width: 68 | Height: 68 | Size: 1.5 KiB |
After Width: 56 | Height: 56 | Size: 4.1 KiB |
After Width: 64 | Height: 64 | Size: 3.9 KiB |
After Width: 60 | Height: 60 | Size: 2.4 KiB |
After Width: 104 | Height: 104 | Size: 4.8 KiB |
After Width: 1500 | Height: 960 | Size: 16 KiB |
After Width: 104 | Height: 104 | Size: 5.0 KiB |
After Width: 104 | Height: 104 | Size: 5.7 KiB |
After Width: 80 | Height: 80 | Size: 3.4 KiB |
After Width: 88 | Height: 80 | Size: 3.8 KiB |
After Width: 44 | Height: 44 | Size: 807 B |
After Width: 44 | Height: 44 | Size: 954 B |
After Width: 348 | Height: 344 | Size: 14 KiB |
After Width: 87 | Height: 87 | Size: 1.1 KiB |
After Width: 87 | Height: 87 | Size: 1.0 KiB |
After Width: 87 | Height: 87 | Size: 1.8 KiB |
After Width: 87 | Height: 87 | Size: 2.1 KiB |
@ -0,0 +1,131 @@ |
|||
@import './mixin/flex.scss'; // flex(主轴,交叉轴,方向,换行,多轴线对齐) / flex-self(对齐,(布满||固定),顺序) |
|||
@import './mixin/text-overflow.scss'; // 文本格式化超出省略号 参数:宽度,单行/多行 |
|||
@import './mixin/position-absolute.scss'; // 绝对定位 参数:上,右,下,左 |
|||
@import './mixin/triangle.scss'; // 画三角形 参数:宽度,朝向,颜色 |
|||
@import './mixin/hr.scss'; // 添加分割线 参数:位置,间隔 |
|||
@import './mixin/price-before.scss'; // 价格¥前加 |
|||
/*colorui css */ |
|||
@import "static/colorui/main.css"; |
|||
@import "static/colorui/icon.css"; |
|||
/*uview-ui css */ |
|||
@import "uview-ui/index.scss"; |
|||
|
|||
page { |
|||
min-height: 100%; |
|||
background-color: #f7f7f7; |
|||
} |
|||
|
|||
/*flex布局(colorui里面也有相关基础样式)*/ |
|||
/* x水平排列*/ |
|||
.x-f { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
/*x两端且水平居中*/ |
|||
.x-bc { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
|
|||
/*x平分且水平居中*/ |
|||
.x-ac { |
|||
display: flex; |
|||
justify-content: space-around; |
|||
align-items: center; |
|||
} |
|||
|
|||
/*x水平靠上对齐*/ |
|||
.x-start { |
|||
display: flex; |
|||
align-items: flex-start; |
|||
} |
|||
|
|||
/*x水平靠下对齐*/ |
|||
.x-end { |
|||
display: flex; |
|||
align-items: flex-end; |
|||
} |
|||
|
|||
/*上下左右居中*/ |
|||
.x-c { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
/*y竖直靠左*/ |
|||
.y-start { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: flex-start; |
|||
} |
|||
|
|||
/*y竖直靠右*/ |
|||
.y-end { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: flex-end; |
|||
} |
|||
|
|||
/*y竖直居中*/ |
|||
.y-f { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
} |
|||
|
|||
/* y竖直两端*/ |
|||
.y-b { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
/*y竖直两端居中*/ |
|||
.y-bc { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
//常用flex公共代码 |
|||
.flex_row{ |
|||
display: flex; |
|||
flex-direction: row; |
|||
flex-wrap: nowrap; |
|||
// flex-flow: row nowrap; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
.flex_col{ |
|||
display: flex; |
|||
flex-direction: column; |
|||
flex-wrap: nowrap; |
|||
// flex-flow: column nowrap; //部分环境不支持简写 |
|||
justify-content: center; //主轴上的对齐方式 |
|||
align-items: center; //交叉轴上如何对齐 |
|||
} |
|||
.flex_col.flex_wrap{flex-wrap: wrap;} |
|||
.flex_row.flex_wrap{flex-wrap: wrap;} |
|||
//覆盖主轴属性 |
|||
.flex_between{justify-content: space-between;} |
|||
.flex_around{justify-content: space-around;} |
|||
.flex_start{justify-content: flex-start;} |
|||
.flex_end{justify-content: flex-end;} |
|||
//覆盖交叉轴属性 |
|||
.flex_between_y{align-items: space-between;} |
|||
.flex_around_y{align-items: space-around;} |
|||
.flex_start_y{align-items: flex-start;} |
|||
.flex_end_y{align-items: flex-end;} |
|||
|
|||
.text-maxline-one { |
|||
text-overflow: ellipsis; |
|||
-webkit-line-clamp: 1; |
|||
-webkit-box-orient: vertical; |
|||
word-break: break-all; |
|||
overflow: hidden; |
|||
display: -webkit-box; |
|||
} |
@ -0,0 +1,164 @@ |
|||
// flex(主轴,交叉轴,方向,换行,多轴线对齐) |
|||
@mixin flex($justify: null, $align: null, $direction: null, $warp: null, $warpAlign: null) { |
|||
display: flex; |
|||
|
|||
@if $direction != null { |
|||
@include flex-direction($direction); |
|||
} |
|||
@if $justify != null { |
|||
@include flex-justify($justify); |
|||
} |
|||
@if $align != null { |
|||
@include flex-align($align); |
|||
} |
|||
@if $warp != null { |
|||
@include flex-warp($warp); |
|||
} |
|||
@if $warpAlign != null { |
|||
@include flex-warpAlign($warpAlign); |
|||
} |
|||
} |
|||
|
|||
// flex-self(对齐,(布满||固定),顺序) |
|||
@mixin flex-self($flex: null, $align: null, $order: null){ |
|||
@if $flex != null { |
|||
@if $flex == full { |
|||
flex: auto; |
|||
} @else if $flex == keep { |
|||
flex: none; |
|||
} @else { |
|||
@include flexError($flex, 'flex-self'); |
|||
} |
|||
} |
|||
|
|||
@if $align != null { |
|||
@include flex-selfAlign($align); |
|||
} |
|||
|
|||
@if $order != null { |
|||
@include flex-order($order); |
|||
} |
|||
} |
|||
|
|||
// flex错误提示 |
|||
@mixin flexError($param, $type) { |
|||
position: relative; |
|||
background-color: #ff3c00 !important; |
|||
overflow: hidden; |
|||
|
|||
&::after { |
|||
position: absolute; |
|||
bottom: 0; |
|||
right: 0; |
|||
padding: 0.5em; |
|||
color: #ff3c00 !important; |
|||
background-color: white !important; |
|||
font-size: 12px; |
|||
content: 'ErrorParam: #{$param} in #{$type}'; |
|||
} |
|||
} |
|||
|
|||
// 项目的排列方向 |
|||
@mixin flex-direction($direction: row) { |
|||
@if $direction == column { |
|||
flex-direction: column; |
|||
} @else if $direction == row { |
|||
flex-direction: row; |
|||
} @else if $direction == row-reverse { |
|||
flex-direction: row-reverse; |
|||
} @else if $direction == column-reverse { |
|||
flex-direction: column-reverse; |
|||
} @else { |
|||
@include flexError($direction, 'flex-direction'); |
|||
} |
|||
} |
|||
|
|||
// 主轴上的对齐方式 |
|||
@mixin flex-justify($justify: start) { |
|||
@if $justify == start { |
|||
justify-content: start; |
|||
} @else if $justify == center { |
|||
justify-content: center; |
|||
} @else if $justify == end { |
|||
justify-content: flex-end; |
|||
} @else if $justify == between { |
|||
justify-content: space-between; |
|||
} @else if $justify == around { |
|||
justify-content: space-around; |
|||
} @else { |
|||
@include flexError($justify, 'flex-justify'); |
|||
} |
|||
} |
|||
|
|||
// 交叉轴上的对齐方式 |
|||
@mixin flex-align($align: top) { |
|||
@if $align == top { |
|||
align-items: flex-start; |
|||
} @else if $align == center { |
|||
align-items: center; |
|||
} @else if $align == bottom { |
|||
align-items: flex-end; |
|||
} @else { |
|||
@include flexError($align, 'flex-align'); |
|||
} |
|||
} |
|||
|
|||
// 换行 |
|||
@mixin flex-warp($warp: wrap) { |
|||
@if $warp == wrap { |
|||
flex-wrap: wrap; |
|||
} @else if $warp == nowrap { |
|||
flex-wrap: nowrap; |
|||
} @else if $warp == wrap-reverse { |
|||
flex-wrap: wrap-reverse; |
|||
} @else { |
|||
@include flexError($warp, 'flex-wrap'); |
|||
} |
|||
} |
|||
|
|||
// 换行多根轴线的对齐方式,如果项目只有一根轴线,该属性不起作用 |
|||
@mixin flex-warpAlign($align: stretch) { |
|||
@if $align == stretch { |
|||
align-content: stretch; |
|||
} @else if $align == top { |
|||
align-content: flex-start; |
|||
} @else if $align == center { |
|||
align-content: center; |
|||
} @else if $align == bottom { |
|||
align-content: flex-end; |
|||
} @else if $align == between { |
|||
align-content: space-between; |
|||
} @else if $align == around { |
|||
align-content: space-around; |
|||
} @else { |
|||
@include flexError($align, 'flex-wrapAlign'); |
|||
} |
|||
} |
|||
|
|||
// 单个项目有与其他项目不一样的对齐方式 |
|||
@mixin flex-selfAlign($align: auto){ |
|||
@if $align == auto { |
|||
align-self: auto; |
|||
} @else if $align == top { |
|||
align-self: flex-start; |
|||
} @else if $align == center { |
|||
align-self: center; |
|||
} @else if $align == bottom { |
|||
align-self: flex-end; |
|||
} @else if $align == baseline { |
|||
align-self: baseline ; |
|||
} @else if $align == stretch { |
|||
align-self: stretch; |
|||
} @else { |
|||
@include flexError($align, 'flex-self-align'); |
|||
} |
|||
} |
|||
|
|||
// 项目的排列顺序,数值越小,排列越靠前,默认为0 |
|||
@mixin flex-order($order: 0){ |
|||
@if $order == round($order) { |
|||
order: $order; |
|||
} @else { |
|||
@include flexError($order, 'flex-self-order'); |
|||
} |
|||
} |
@ -0,0 +1,26 @@ |
|||
// 添加分割线 参数:位置,间隔 |
|||
@mixin hr($direction: top, $margin: 0){ |
|||
position: absolute; |
|||
z-index: 3; |
|||
@if ($direction == top) { // 右三角 |
|||
top: 0; |
|||
left: $margin; |
|||
right: $margin; |
|||
} @else if ($direction == bottom){ |
|||
bottom: 0; |
|||
left: $margin; |
|||
right: $margin; |
|||
} @else if ($direction == left){ |
|||
top: $margin; |
|||
bottom: $margin; |
|||
left: 0; |
|||
} @else if ($direction == right){ |
|||
top: $margin; |
|||
bottom: $margin; |
|||
right: 0; |
|||
} |
|||
height: 1upx; |
|||
content: ''; |
|||
transform: scaleY(.5); |
|||
background-color: #c8c7cc; |
|||
} |
@ -0,0 +1,16 @@ |
|||
// 绝对定位 参数:上,右,下,左 |
|||
@mixin position-absolute($top: null, $right: null, $bottom: null, $left: null) { |
|||
position: absolute; |
|||
@if ($left!="" and $left!=null) { |
|||
left: $left; |
|||
} |
|||
@if ($right!="" and $right!=null) { |
|||
right: $right; |
|||
} |
|||
@if ($top!="" and $top!=null) { |
|||
top: $top; |
|||
} |
|||
@if ($bottom!="" and $bottom!=null) { |
|||
bottom: $bottom; |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
// 价格¥前加 |
|||
%__priceBefore{ |
|||
font-size: $uni-font-size-sm; |
|||
content: "¥"; |
|||
} |
@ -0,0 +1,16 @@ |
|||
// 文本格式化超出省略号 参数:宽度,单行/多行 |
|||
@mixin text-overflow($width: null, $row: 1){ |
|||
@if $width != null{ |
|||
width: $width; |
|||
} |
|||
@if $row == 1{ |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
} @else { |
|||
display: -webkit-box; |
|||
-webkit-box-orient: vertical; |
|||
-webkit-line-clamp: $row; |
|||
overflow: hidden; |
|||
} |
|||
} |
@ -0,0 +1,16 @@ |
|||
// 画三角形 参数:宽度,朝向,颜色 |
|||
@mixin triangle($width: 10px, $direction: top, $color: $uni-color-main) { |
|||
border: $width solid transparent; |
|||
@if ($direction == top) { // 上三角 |
|||
border-bottom-color: $color; |
|||
} |
|||
@if ($direction == bottom) { // 下三角 |
|||
border-top-color: $color; |
|||
} |
|||
@if ($direction == left) { // 左三角 |
|||
border-right-color: $color; |
|||
} |
|||
@if ($direction == right) { // 右三角 |
|||
border-left-color: $color; |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
|||
<link rel="shortcut icon" type="image/x-icon" href=""> |
|||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> |
|||
<title> |
|||
<%= htmlWebpackPlugin.options.title %> |
|||
</title> |
|||
|
|||
<style> |
|||
::-webkit-scrollbar{ |
|||
display: none; |
|||
} |
|||
</style> |
|||
|
|||
<link rel="stylesheet" href="<%= BASE_URL %>static/index.css" /> |
|||
</head> |
|||
<body> |
|||
<!-- 该文件为 H5 平台的模板 HTML,并非应用入口。 --> |
|||
<!-- 请勿在此文件编写页面代码或直接运行此文件。 --> |
|||
<!-- 详见文档:https://uniapp.dcloud.io/collocation/manifest?id=h5-template --> |
|||
<noscript> |
|||
<strong>本站点必须要开启JavaScript才能运行</strong> |
|||
</noscript> |
|||
<div id="app"></div> |
|||
<!-- built files will be auto injected --> |
|||
<script> |
|||
/*BAIDU_STAT*/ |
|||
</script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,76 @@ |
|||
/** |
|||
* 这里是uni-app内置的常用样式变量 |
|||
* |
|||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 |
|||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App |
|||
* |
|||
*/ |
|||
|
|||
/** |
|||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 |
|||
* |
|||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 |
|||
*/ |
|||
@import 'uview-ui/theme.scss'; |
|||
/* 颜色变量 */ |
|||
|
|||
/* 行为相关颜色 */ |
|||
$uni-color-primary: #007aff; |
|||
$uni-color-success: #4cd964; |
|||
$uni-color-warning: #f0ad4e; |
|||
$uni-color-error: #dd524d; |
|||
|
|||
/* 文字基本颜色 */ |
|||
$uni-text-color:#333;//基本色 |
|||
$uni-text-color-inverse:#fff;//反色 |
|||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 |
|||
$uni-text-color-placeholder: #808080; |
|||
$uni-text-color-disable:#c0c0c0; |
|||
|
|||
/* 背景颜色 */ |
|||
$uni-bg-color:#ffffff; |
|||
$uni-bg-color-grey:#f8f8f8; |
|||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色 |
|||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 |
|||
|
|||
/* 边框颜色 */ |
|||
$uni-border-color:#c8c7cc; |
|||
|
|||
/* 尺寸变量 */ |
|||
|
|||
/* 文字尺寸 */ |
|||
$uni-font-size-sm:24rpx; |
|||
$uni-font-size-base:28rpx; |
|||
$uni-font-size-lg:32rpx; |
|||
|
|||
/* 图片尺寸 */ |
|||
$uni-img-size-sm:40rpx; |
|||
$uni-img-size-base:52rpx; |
|||
$uni-img-size-lg:80rpx; |
|||
|
|||
/* Border Radius */ |
|||
$uni-border-radius-sm: 4rpx; |
|||
$uni-border-radius-base: 6rpx; |
|||
$uni-border-radius-lg: 12rpx; |
|||
$uni-border-radius-circle: 50%; |
|||
|
|||
/* 水平间距 */ |
|||
$uni-spacing-row-sm: 10px; |
|||
$uni-spacing-row-base: 20rpx; |
|||
$uni-spacing-row-lg: 30rpx; |
|||
|
|||
/* 垂直间距 */ |
|||
$uni-spacing-col-sm: 8rpx; |
|||
$uni-spacing-col-base: 16rpx; |
|||
$uni-spacing-col-lg: 24rpx; |
|||
|
|||
/* 透明度 */ |
|||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 |
|||
|
|||
/* 文章场景相关 */ |
|||
$uni-color-title: #2C405A; // 文章标题颜色 |
|||
$uni-font-size-title:40rpx; |
|||
$uni-color-subtitle: #555555; // 二级标题颜色 |
|||
$uni-font-size-subtitle:36rpx; |
|||
$uni-color-paragraph: #3F536E; // 文章段落颜色 |
|||
$uni-font-size-paragraph:30rpx; |
@ -0,0 +1,21 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2020 www.uviewui.com |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -0,0 +1,105 @@ |
|||
<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://www.uviewui.com/components/quickstart.html)了解更详细的内容 |
|||
|
|||
## 使用方法 |
|||
配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。 |
|||
|
|||
```html |
|||
<template> |
|||
<u-button>按钮</u-button> |
|||
</template> |
|||
``` |
|||
|
|||
请通过[快速上手](https://www.uviewui.com/components/quickstart.html)了解更详细的内容 |
|||
|
|||
## 链接 |
|||
|
|||
- [官方文档](https://www.uviewui.com/) |
|||
- [更新日志](https://www.www.uviewui.com/components/changelog.html) |
|||
- [升级指南](https://www.uviewui.com/components/changelog.html) |
|||
- [关于我们](https://www.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/alipay.png" width="220" ><img style="margin-left: 100px;" src="https://uviewui.com/common/wechat.png" width="220" > |
|||
|
|||
## 版权信息 |
|||
uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。 |
@ -0,0 +1,35 @@ |
|||
## 2.0.3(2021-11-16) |
|||
## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU) |
|||
|
|||
# uView2.0重磅发布,利剑出鞘,一统江湖 |
|||
|
|||
1. uView2.0已实现全面兼容nvue |
|||
2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升 |
|||
3. 目前uView2.0为公测阶段,相关细节可能会有变动 |
|||
4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html) |
|||
5. 处理modal的confirm回调事件拼写错误问题 |
|||
6. 处理input组件@input事件参数错误问题 |
|||
7. 其他一些修复 |
|||
## 2.0.2(2021-11-16) |
|||
## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU) |
|||
|
|||
# uView2.0重磅发布,利剑出鞘,一统江湖 |
|||
|
|||
1. uView2.0已实现全面兼容nvue |
|||
2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升 |
|||
3. 目前uView2.0为公测阶段,相关细节可能会有变动 |
|||
4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html) |
|||
5. 修复input组件formatter参数缺失问题 |
|||
6. 优化loading-icon组件的scss写法问题,防止不兼容新版本scss |
|||
## 2.0.0(2020-11-15) |
|||
## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU) |
|||
|
|||
# uView2.0重磅发布,利剑出鞘,一统江湖 |
|||
|
|||
1. uView2.0已实现全面兼容nvue |
|||
2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升 |
|||
3. 目前uView2.0为公测阶段,相关细节可能会有变动 |
|||
4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html) |
|||
5. 修复input组件formatter参数缺失问题 |
|||
|
|||
|
@ -0,0 +1,74 @@ |
|||
<template> |
|||
<uvForm |
|||
ref="uForm" |
|||
:model="model" |
|||
:rules="rules" |
|||
:errorType="errorType" |
|||
:borderBottom="borderBottom" |
|||
:labelPosition="labelPosition" |
|||
:labelWidth="labelWidth" |
|||
:labelAlign="labelAlign" |
|||
:labelStyle="labelStyle" |
|||
:customStyle="customStyle" |
|||
> |
|||
<slot /> |
|||
</uvForm> |
|||
</template> |
|||
|
|||
<script> |
|||
/** |
|||
* 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件 |
|||
* 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转 |
|||
*/ |
|||
import uvForm from '../u-form/u-form.vue'; |
|||
import props from '../u-form/props.js' |
|||
export default { |
|||
// #ifdef MP-WEIXIN |
|||
name: 'u-form', |
|||
// #endif |
|||
// #ifndef MP-WEIXIN |
|||
name: 'u--form', |
|||
// #endif |
|||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin], |
|||
components: { |
|||
uvForm |
|||
}, |
|||
created() { |
|||
this.children = [] |
|||
}, |
|||
methods: { |
|||
validate() { |
|||
/** |
|||
* 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form |
|||
* 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的 |
|||
* 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children |
|||
*/ |
|||
// #ifdef MP-WEIXIN |
|||
this.setMpData() |
|||
// #endif |
|||
return this.$refs.uForm.validate() |
|||
}, |
|||
validateField(value, callback) { |
|||
// #ifdef MP-WEIXIN |
|||
this.setMpData() |
|||
// #endif |
|||
return this.$refs.uForm.validateField(value, callback) |
|||
}, |
|||
resetFields() { |
|||
// #ifdef MP-WEIXIN |
|||
this.setMpData() |
|||
// #endif |
|||
return this.$refs.uForm.resetFields() |
|||
}, |
|||
clearValidate(props) { |
|||
// #ifdef MP-WEIXIN |
|||
this.setMpData() |
|||
// #endif |
|||
return this.$refs.uForm.clearValidate(props) |
|||
}, |
|||
setMpData() { |
|||
this.$refs.uForm.children = this.children |
|||
} |
|||
}, |
|||
} |
|||
</script> |
@ -0,0 +1,40 @@ |
|||
<template> |
|||
<uvImage |
|||
:src="src" |
|||
:mode="mode" |
|||
:width="width" |
|||
:height="height" |
|||
:shape="shape" |
|||
:radius="radius" |
|||
:lazyLoad="lazyLoad" |
|||
:showMenuByLongpress="showMenuByLongpress" |
|||
:loadingIcon="loadingIcon" |
|||
:errorIcon="errorIcon" |
|||
:showLoading="showLoading" |
|||
:showError="showError" |
|||
:fade="fade" |
|||
:webp="webp" |
|||
:duration="duration" |
|||
:bgColor="bgColor" |
|||
:customStyle="customStyle" |
|||
@click="$emit('click')" |
|||
@error="$emit('error')" |
|||
@load="$emit('load')" |
|||
></uvImage> |
|||
</template> |
|||
|
|||
<script> |
|||
/** |
|||
* 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件 |
|||
* 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转 |
|||
*/ |
|||
import uvImage from '../u-image/u-image.vue'; |
|||
import props from '../u-image/props.js'; |
|||
export default { |
|||
name: 'u--image', |
|||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin], |
|||
components: { |
|||
uvImage |
|||
}, |
|||
} |
|||
</script> |
@ -0,0 +1,63 @@ |
|||
<template> |
|||
<uvInput |
|||
:value="value" |
|||
:type="type" |
|||
:fixed="fixed" |
|||
:disabled="disabled" |
|||
:disabledColor="disabledColor" |
|||
:clearable="clearable" |
|||
:password="password" |
|||
:maxlength="maxlength" |
|||
:placeholder="placeholder" |
|||
:placeholderClass="placeholderClass" |
|||
:placeholderStyle="placeholderStyle" |
|||
:showWordLimit="showWordLimit" |
|||
:confirmType="confirmType" |
|||
:confirmHold="confirmHold" |
|||
:holdKeyboard="holdKeyboard" |
|||
:focus="focus" |
|||
:autoBlur="autoBlur" |
|||
:disableDefaultPadding="disableDefaultPadding" |
|||
:cursor="cursor" |
|||
:cursorSpacing="cursorSpacing" |
|||
:selectionStart="selectionStart" |
|||
:selectionEnd="selectionEnd" |
|||
:adjustPosition="adjustPosition" |
|||
:inputAlign="inputAlign" |
|||
:autosize="autosize" |
|||
:fontSize="fontSize" |
|||
:color="color" |
|||
:prefixIcon="prefixIcon" |
|||
:suffixIcon="suffixIcon" |
|||
:suffixIconStyle="suffixIconStyle" |
|||
:prefixIconStyle="prefixIconStyle" |
|||
:border="border" |
|||
:readonly="readonly" |
|||
:shape="shape" |
|||
:customStyle="customStyle" |
|||
:formatter="formatter" |
|||
@focus="$emit('focus')" |
|||
@blur="$emit('blur')" |
|||
@keyboardheightchange="$emit('keyboardheightchange')" |
|||
@change="e => $emit('change', e)" |
|||
@input="e => $emit('input', e)" |
|||
@clear="$emit('clear')" |
|||
@click="$emit('click')" |
|||
></uvInput> |
|||
</template> |
|||
|
|||
<script> |
|||
/** |
|||
* 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件 |
|||
* 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转 |
|||
*/ |
|||
import uvInput from '../u-input/u-input.vue'; |
|||
import props from '../u-input/props.js' |
|||
export default { |
|||
name: 'u--input', |
|||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin], |
|||
components: { |
|||
uvInput |
|||
}, |
|||
} |
|||
</script> |
@ -0,0 +1,46 @@ |
|||
<template> |
|||
<uvText |
|||
:type="type" |
|||
:show="show" |
|||
:text="text" |
|||
:prefixIcon="prefixIcon" |
|||
:suffixIcon="suffixIcon" |
|||
:mode="mode" |
|||
:href="href" |
|||
:format="format" |
|||
:call="call" |
|||
:encrypt="encrypt" |
|||
:openType="openType" |
|||
:bold="bold" |
|||
:block="block" |
|||
:lines="lines" |
|||
:color="color" |
|||
:size="size" |
|||
:iconStyle="iconStyle" |
|||
:precision="precision" |
|||
:decoration="decoration" |
|||
:margin="margin" |
|||
:lineHeight="lineHeight" |
|||
:align="align" |
|||
:wordWrap="wordWrap" |
|||
:customStyle="customStyle" |
|||
@click="$emit('click')" |
|||
></uvText> |
|||
</template> |
|||
|
|||
<script> |
|||
/** |
|||
* 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件 |
|||
* 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转 |
|||
* 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法 |
|||
*/ |
|||
import uvText from '../u-text/u-text.vue' |
|||
import props from '../u-text/props.js' |
|||
export default { |
|||
name: 'u--text', |
|||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin], |
|||
components: { |
|||
uvText |
|||
}, |
|||
} |
|||
</script> |
@ -0,0 +1,47 @@ |
|||
<template> |
|||
<uvTextarea |
|||
:value="value" |
|||
:placeholder="placeholder" |
|||
:height="height" |
|||
:confirmType="confirmType" |
|||
:disabled="disabled" |
|||
:count="count" |
|||
:focus="focus" |
|||
:autoHeight="autoHeight" |
|||
:fixed="fixed" |
|||
:cursorSpacing="cursorSpacing" |
|||
:cursor="cursor" |
|||
:showConfirmBar="showConfirmBar" |
|||
:selectionStart="selectionStart" |
|||
:selectionEnd="selectionEnd" |
|||
:adjustPosition="adjustPosition" |
|||
:disableDefaultPadding="disableDefaultPadding" |
|||
:holdKeyboard="holdKeyboard" |
|||
:maxlength="maxlength" |
|||
:border="border" |
|||
:customStyle="customStyle" |
|||
:formatter="formatter" |
|||
@focus="$emit('focus')" |
|||
@blur="$emit('blur')" |
|||
@linechange="$emit('linechange')" |
|||
@confirm="$emit('confirm')" |
|||
@input="e => $emit('input', e)" |
|||
@keyboardheightchange="$emit('keyboardheightchange')" |
|||
></uvTextarea> |
|||
</template> |
|||
|
|||
<script> |
|||
/** |
|||
* 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件 |
|||
* 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转 |
|||
*/ |
|||
import uvTextarea from '../u-textarea/u-textarea.vue'; |
|||
import props from '../u-textarea/props.js' |
|||
export default { |
|||
name: 'u--textarea', |
|||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin], |
|||
components: { |
|||
uvTextarea |
|||
}, |
|||
} |
|||
</script> |
@ -0,0 +1,54 @@ |
|||
export default { |
|||
props: { |
|||
// 操作菜单是否展示 (默认false)
|
|||
show: { |
|||
type: Boolean, |
|||
default: uni.$u.props.actionSheet.show |
|||
}, |
|||
// 标题
|
|||
title: { |
|||
type: String, |
|||
default: uni.$u.props.actionSheet.title |
|||
}, |
|||
// 选项上方的描述信息
|
|||
description: { |
|||
type: String, |
|||
default: uni.$u.props.actionSheet.description |
|||
}, |
|||
// 数据
|
|||
actions: { |
|||
type: Array, |
|||
default: uni.$u.props.actionSheet.actions |
|||
}, |
|||
// 取消按钮的文字,不为空时显示按钮
|
|||
cancelText: { |
|||
type: String, |
|||
default: uni.$u.props.actionSheet.cancelText |
|||
}, |
|||
// 点击某个菜单项时是否关闭弹窗
|
|||
closeOnClickAction: { |
|||
type: Boolean, |
|||
default: uni.$u.props.actionSheet.closeOnClickAction |
|||
}, |
|||
// 处理底部安全区(默认true)
|
|||
safeAreaInsetBottom: { |
|||
type: Boolean, |
|||
default: uni.$u.props.actionSheet.safeAreaInsetBottom |
|||
}, |
|||
// 小程序的打开方式
|
|||
openType: { |
|||
type: String, |
|||
default: uni.$u.props.actionSheet.openType |
|||
}, |
|||
// 点击遮罩是否允许关闭 (默认true)
|
|||
closeOnClickOverlay: { |
|||
type: Boolean, |
|||
default: uni.$u.props.actionSheet.closeOnClickOverlay |
|||
}, |
|||
// 是否显示圆角 (默认false)
|
|||
round: { |
|||
type: Boolean, |
|||
default: uni.$u.props.actionSheet.round |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,275 @@ |
|||
|
|||
<template> |
|||
<u-popup |
|||
:show="show" |
|||
mode="bottom" |
|||
@close="close" |
|||
:closeOnClickOverlay="closeOnClickOverlay" |
|||
:safeAreaInsetBottom="safeAreaInsetBottom" |
|||
:round="round" |
|||
> |
|||
<view class="u-action-sheet"> |
|||
<view |
|||
class="u-action-sheet__header" |
|||
v-if="title" |
|||
> |
|||
<text class="u-action-sheet__header__title u-line-1">{{title}}</text> |
|||
<view |
|||
class="u-action-sheet__header__icon-wrap" |
|||
@tap.stop="close" |
|||
> |
|||
<u-icon |
|||
name="close" |
|||
size="17" |
|||
color="#c8c9cc" |
|||
bold |
|||
></u-icon> |
|||
</view> |
|||
</view> |
|||
<text |
|||
class="u-action-sheet__description" |
|||
:style="[{ |
|||
marginTop: `${title && description ? 0 : '18px'}` |
|||
}]" |
|||
v-if="description" |
|||
>{{description}}</text> |
|||
<slot> |
|||
<u-line v-if="description"></u-line> |
|||
<view class="u-action-sheet__item-wrap"> |
|||
<template v-for="(item, index) in actions"> |
|||
<!-- #ifdef MP --> |
|||
<button |
|||
:key="index" |
|||
class="u-reset-button" |
|||
:openType="item.openType" |
|||
@getuserinfo="onGetUserInfo" |
|||
@contact="onContact" |
|||
@getphonenumber="onGetPhoneNumber" |
|||
@error="onError" |
|||
@launchapp="onLaunchApp" |
|||
@opensetting="onOpenSetting" |
|||
:lang="lang" |
|||
:session-from="sessionFrom" |
|||
:send-message-title="sendMessageTitle" |
|||
:send-message-path="sendMessagePath" |
|||
:send-message-img="sendMessageImg" |
|||
:show-message-card="showMessageCard" |
|||
:app-parameter="appParameter" |
|||
@tap="selectHandler(index)" |
|||
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''" |
|||
> |
|||
<!-- #endif --> |
|||
<view |
|||
class="u-action-sheet__item-wrap__item" |
|||
@tap.stop="selectHandler(index)" |
|||
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''" |
|||
:hover-stay-time="150" |
|||
> |
|||
<template v-if="!item.loading"> |
|||
<text |
|||
class="u-action-sheet__item-wrap__item__name" |
|||
:style="[itemStyle(index)]" |
|||
>{{ item.name }}</text> |
|||
<text |
|||
v-if="item.subname" |
|||
class="u-action-sheet__item-wrap__item__subname" |
|||
>{{ item.subname }}</text> |
|||
</template> |
|||
<u-loading-icon |
|||
v-else |
|||
custom-class="van-action-sheet__loading" |
|||
size="18" |
|||
mode="circle" |
|||
/> |
|||
</view> |
|||
<!-- #ifdef MP --> |
|||
</button> |
|||
<!-- #endif --> |
|||
<u-line v-if="index !== actions.length - 1"></u-line> |
|||
</template> |
|||
</view> |
|||
</slot> |
|||
<u-gap |
|||
bgColor="#eaeaec" |
|||
height="6" |
|||
v-if="cancelText" |
|||
></u-gap> |
|||
<view hover-class="u-action-sheet--hover"> |
|||
<text |
|||
@touchmove.stop.prevent |
|||
:hover-stay-time="150" |
|||
v-if="cancelText" |
|||
class="u-action-sheet__cancel-text" |
|||
@tap="close" |
|||
>{{cancelText}}</text> |
|||
</view> |
|||
</view> |
|||
</u-popup> |
|||
</template> |
|||
|
|||
<script> |
|||
import openType from '../../libs/mixin/openType' |
|||
import button from '../../libs/mixin/button' |
|||
import props from './props.js'; |
|||
/** |
|||
* ActionSheet 操作菜单 |
|||
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。 |
|||
* @tutorial https://www.uviewui.com/components/actionSheet.html |
|||
* |
|||
* @property {Boolean} show 操作菜单是否展示 (默认 false ) |
|||
* @property {String} title 操作菜单标题 |
|||
* @property {String} description 选项上方的描述信息 |
|||
* @property {Array<Object>} actions 按钮的文字数组,见官方文档示例 |
|||
* @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮 |
|||
* @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true ) |
|||
* @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true ) |
|||
* @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error ) |
|||
* @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true ) |
|||
* @property {Boolean} round 是否显示圆角 (默认 false ) |
|||
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文 |
|||
* @property {String} sessionFrom 会话来源,openType="contact"时有效 |
|||
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效 |
|||
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效 |
|||
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效 |
|||
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false ) |
|||
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效 |
|||
* |
|||
* @event {Function} select 点击ActionSheet列表项时触发 |
|||
* @event {Function} close 点击取消按钮时触发 |
|||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效 |
|||
* @event {Function} contact 客服消息回调,openType="contact"时有效 |
|||
* @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效 |
|||
* @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效 |
|||
* @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效 |
|||
* @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效 |
|||
* @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet> |
|||
*/ |
|||
export default { |
|||
name: "u-action-sheet", |
|||
// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到 |
|||
mixins: [openType, button, uni.$u.mixin, props], |
|||
data() { |
|||
return { |
|||
|
|||
} |
|||
}, |
|||
computed: { |
|||
// 操作项目的样式 |
|||
itemStyle() { |
|||
return (index) => { |
|||
let style = {}; |
|||
if (this.actions[index].color) style.color = this.actions[index].color |
|||
if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize) |
|||
// 选项被禁用的样式 |
|||
if (this.actions[index].disabled) style.color = '#c0c4cc' |
|||
return style; |
|||
} |
|||
}, |
|||
}, |
|||
methods: { |
|||
close() { |
|||
// 允许点击遮罩关闭时,才发出close事件 |
|||
if(this.closeOnClickOverlay) { |
|||
this.$emit('close') |
|||
} |
|||
}, |
|||
selectHandler(index) { |
|||
const item = this.actions[index] |
|||
if (item && !item.disabled && !item.loading) { |
|||
this.$emit('select', item) |
|||
if (this.closeOnClickAction) { |
|||
this.$emit('close') |
|||
} |
|||
} |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "../../libs/css/components.scss"; |
|||
$u-action-sheet-reset-button-width:100% !default; |
|||
$u-action-sheet-title-font-size: 16px !default; |
|||
$u-action-sheet-title-padding: 12px 30px !default; |
|||
$u-action-sheet-title-color: $u-main-color !default; |
|||
$u-action-sheet-header-icon-wrap-right:15px !default; |
|||
$u-action-sheet-header-icon-wrap-top:15px !default; |
|||
$u-action-sheet-description-font-size:13px !default; |
|||
$u-action-sheet-description-color:14px !default; |
|||
$u-action-sheet-description-margin: 18px 15px !default; |
|||
$u-action-sheet-item-wrap-item-padding:15px !default; |
|||
$u-action-sheet-item-wrap-name-font-size:16px !default; |
|||
$u-action-sheet-item-wrap-subname-font-size:13px !default; |
|||
$u-action-sheet-item-wrap-subname-color: #c0c4cc !default; |
|||
$u-action-sheet-item-wrap-subname-margin-top:10px !default; |
|||
$u-action-sheet-cancel-text-font-size:16px !default; |
|||
$u-action-sheet-cancel-text-color:$u-content-color !default; |
|||
$u-action-sheet-cancel-text-font-size:15px !default; |
|||
$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default; |
|||
|
|||
.u-reset-button { |
|||
width: $u-action-sheet-reset-button-width; |
|||
} |
|||
|
|||
.u-action-sheet { |
|||
text-align: center; |
|||
&__header { |
|||
position: relative; |
|||
padding: $u-action-sheet-title-padding; |
|||
&__title { |
|||
font-size: $u-action-sheet-title-font-size; |
|||
color: $u-action-sheet-title-color; |
|||
font-weight: bold; |
|||
text-align: center; |
|||
} |
|||
|
|||
&__icon-wrap { |
|||
position: absolute; |
|||
right: $u-action-sheet-header-icon-wrap-right; |
|||
top: $u-action-sheet-header-icon-wrap-top; |
|||
} |
|||
} |
|||
|
|||
&__description { |
|||
font-size: $u-action-sheet-description-font-size; |
|||
color: $u-tips-color; |
|||
margin: $u-action-sheet-description-margin; |
|||
text-align: center; |
|||
} |
|||
|
|||
&__item-wrap { |
|||
|
|||
&__item { |
|||
padding: $u-action-sheet-item-wrap-item-padding; |
|||
@include flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
flex-direction: column; |
|||
|
|||
&__name { |
|||
font-size: $u-action-sheet-item-wrap-name-font-size; |
|||
color: $u-main-color; |
|||
text-align: center; |
|||
} |
|||
|
|||
&__subname { |
|||
font-size: $u-action-sheet-item-wrap-subname-font-size; |
|||
color: $u-action-sheet-item-wrap-subname-color; |
|||
margin-top: $u-action-sheet-item-wrap-subname-margin-top; |
|||
text-align: center; |
|||
} |
|||
} |
|||
} |
|||
|
|||
&__cancel-text { |
|||
font-size: $u-action-sheet-cancel-text-font-size; |
|||
color: $u-action-sheet-cancel-text-color; |
|||
text-align: center; |
|||
padding: $u-action-sheet-cancel-text-font-size; |
|||
} |
|||
|
|||
&--hover { |
|||
background-color: $u-action-sheet-cancel-text-hover-background-color; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,59 @@ |
|||
export default { |
|||
props: { |
|||
// 图片地址,Array<String>|Array<Object>形式
|
|||
urls: { |
|||
type: Array, |
|||
default: uni.$u.props.album.urls |
|||
}, |
|||
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
|||
keyName: { |
|||
type: String, |
|||
default: uni.$u.props.album.keyName |
|||
}, |
|||
// 单图时,图片长边的长度
|
|||
singleSize: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.album.singleSize |
|||
}, |
|||
// 多图时,图片边长
|
|||
multipleSize: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.album.multipleSize |
|||
}, |
|||
// 多图时,图片水平和垂直之间的间隔
|
|||
space: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.album.space |
|||
}, |
|||
// 单图时,图片缩放裁剪的模式
|
|||
singleMode: { |
|||
type: String, |
|||
default: uni.$u.props.album.singleMode |
|||
}, |
|||
// 多图时,图片缩放裁剪的模式
|
|||
multipleMode: { |
|||
type: String, |
|||
default: uni.$u.props.album.multipleMode |
|||
}, |
|||
// 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
|
|||
maxCount: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.album.maxCount |
|||
}, |
|||
// 是否可以预览图片
|
|||
previewFullImage: { |
|||
type: Boolean, |
|||
default: uni.$u.props.album.previewFullImage |
|||
}, |
|||
// 每行展示图片数量,如设置,singleSize和multipleSize将会无效
|
|||
rowCount: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.album.rowCount |
|||
}, |
|||
// 超出maxCount时是否显示查看更多的提示
|
|||
showMore: { |
|||
type: Boolean, |
|||
default: uni.$u.props.album.showMore |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,236 @@ |
|||
<template> |
|||
<view class="u-album"> |
|||
<view |
|||
class="u-album__row" |
|||
ref="u-album__row" |
|||
v-for="(arr, index) in showUrls" |
|||
:forComputedUse="albumWidth" |
|||
:key="index" |
|||
> |
|||
<view |
|||
class="u-album__row__wrapper" |
|||
v-for="(item, index1) in arr" |
|||
:key="index1" |
|||
:style="[imageStyle(index + 1, index1 + 1)]" |
|||
@tap="onPreviewTap(getSrc(item))" |
|||
> |
|||
<image |
|||
:src="getSrc(item)" |
|||
:mode="urls.length === 1 ? (imageHeight > 0 ? singleMode : 'widthFix') : multipleMode" |
|||
:style="[{ |
|||
width: $u.addUnit(imageWidth), |
|||
height: $u.addUnit(imageHeight) |
|||
}]" |
|||
></image> |
|||
<view |
|||
v-if="showMore && urls.length > rowCount * showUrls.length && index === showUrls.length - 1 && index1 === showUrls[showUrls.length - 1].length - 1" |
|||
class="u-album__row__wrapper__text" |
|||
> |
|||
<u--text |
|||
:text="`+${urls.length - maxCount}`" |
|||
color="#fff" |
|||
:size="multipleSize * 0.3" |
|||
align="center" |
|||
customStyle="justify-content: center" |
|||
></u--text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import props from './props.js'; |
|||
// #ifdef APP-NVUE |
|||
// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度 |
|||
const dom = uni.requireNativePlugin('dom') |
|||
// #endif |
|||
|
|||
/** |
|||
* Album 相册 |
|||
* @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码 |
|||
* @tutorial https://www.uviewui.com/components/album.html |
|||
* |
|||
* @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式 |
|||
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址 |
|||
* @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 ) |
|||
* @property {String | Number} multipleSize 多图时,图片边长 (默认 70 ) |
|||
* @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 ) |
|||
* @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' ) |
|||
* @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' ) |
|||
* @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 ) |
|||
* @property {Boolean} previewFullImage 是否可以预览图片 (默认 true ) |
|||
* @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 ) |
|||
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true ) |
|||
* |
|||
* @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width ) |
|||
* @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album> |
|||
*/ |
|||
export default { |
|||
name: 'u-album', |
|||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props], |
|||
data() { |
|||
return { |
|||
// 单图的宽度 |
|||
singleWidth: 0, |
|||
// 单图的高度 |
|||
singleHeight: 0, |
|||
// 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比 |
|||
singlePercent: 0.6, |
|||
} |
|||
}, |
|||
watch: { |
|||
urls: { |
|||
immediate: true, |
|||
handler(newVal) { |
|||
if (newVal.length === 1) { |
|||
this.getImageRect() |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
imageStyle() { |
|||
return (index1, index2) => { |
|||
const { |
|||
space, |
|||
rowCount, |
|||
multipleSize, |
|||
urls |
|||
} = this, { |
|||
addUnit, |
|||
addStyle |
|||
} = uni.$u, |
|||
rowLen = this.showUrls.length, |
|||
allLen = this.urls.length |
|||
const style = { |
|||
marginRight: addUnit(space), |
|||
marginBottom: addUnit(space) |
|||
} |
|||
// 如果为最后一行,则每个图片都无需下边框 |
|||
if (index1 === rowLen) style.marginBottom = 0 |
|||
// 每行的最右边一张和总长度的最后一张无需右边框 |
|||
if (index2 === rowCount || index1 === rowLen && index2 === this.showUrls[index1 - 1].length) style.marginRight = |
|||
0 |
|||
return style |
|||
} |
|||
}, |
|||
// 将数组划分为二维数组 |
|||
showUrls() { |
|||
const arr = [] |
|||
this.urls.map((item, index) => { |
|||
// 限制最大展示数量 |
|||
if(index + 1 <= this.maxCount) { |
|||
// 计算该元素为第几个素组内 |
|||
const itemIndex = Math.floor(index / this.rowCount) |
|||
// 判断对应的索引是否存在 |
|||
if (!arr[itemIndex]) { |
|||
arr[itemIndex] = [] |
|||
} |
|||
arr[itemIndex].push(item) |
|||
} |
|||
}) |
|||
return arr |
|||
}, |
|||
imageWidth() { |
|||
return this.urls.length === 1 ? this.singleWidth : this.multipleSize |
|||
}, |
|||
imageHeight() { |
|||
return this.urls.length === 1 ? this.singleHeight : this.multipleSize |
|||
}, |
|||
// 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度 |
|||
// 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送 |
|||
albumWidth() { |
|||
let width = 0 |
|||
if(this.urls.length === 1) { |
|||
width = this.singleWidth |
|||
} else { |
|||
width = this.showUrls[0].length * this.multipleSize + this.space * (this.showUrls[0].length - 1) |
|||
} |
|||
this.$emit('albumWidth', width) |
|||
return width |
|||
} |
|||
}, |
|||
methods: { |
|||
// 预览图片 |
|||
onPreviewTap(url) { |
|||
const urls = this.urls.map(item => { |
|||
return this.getSrc(item) |
|||
}) |
|||
uni.previewImage({ |
|||
current: url, |
|||
urls |
|||
}); |
|||
}, |
|||
// 获取图片的路径 |
|||
getSrc(item) { |
|||
return uni.$u.test.object(item) ? this.keyName && item[this.keyName] || item.src : item |
|||
}, |
|||
// 单图时,获取图片的尺寸 |
|||
// 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸 |
|||
// 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent) |
|||
getImageRect() { |
|||
const src = this.getSrc(this.urls[0]) |
|||
uni.getImageInfo({ |
|||
src, |
|||
success: (res) => { |
|||
// 判断图片横向还是竖向展示方式 |
|||
const isHorizotal = res.width >= res.height |
|||
this.singleWidth = isHorizotal ? this.singleSize : res.width / res.height * this.singleSize |
|||
this.singleHeight = !isHorizotal ? this.singleSize : res.height / res.width * this.singleWidth |
|||
}, |
|||
fail: () => { |
|||
this.getComponentWidth() |
|||
} |
|||
}) |
|||
}, |
|||
// 获取组件的宽度 |
|||
async getComponentWidth() { |
|||
// 延时一定时间,以获取dom尺寸 |
|||
await uni.$u.sleep(30) |
|||
// #ifndef APP-NVUE |
|||
this.$uGetRect('.u-album__row').then(size => { |
|||
this.singleWidth = size.width * this.singlePercent |
|||
}) |
|||
// #endif |
|||
|
|||
// #ifdef APP-NVUE |
|||
// 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组 |
|||
const ref = this.$refs['u-album__row'][0] |
|||
ref && dom.getComponentRect(ref, (res) => { |
|||
this.singleWidth = res.size.width * this.singlePercent |
|||
}) |
|||
// #endif |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "../../libs/css/components.scss"; |
|||
|
|||
.u-album { |
|||
@include flex(column); |
|||
|
|||
&__row { |
|||
@include flex(row); |
|||
flex-wrap: wrap; |
|||
|
|||
&__wrapper { |
|||
position: relative; |
|||
|
|||
&__text { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: rgba(0, 0, 0, 0.3); |
|||
@include flex(row); |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,44 @@ |
|||
export default { |
|||
props: { |
|||
// 显示文字
|
|||
title: { |
|||
type: String, |
|||
default: uni.$u.props.alert.title |
|||
}, |
|||
// 主题,success/warning/info/error
|
|||
type: { |
|||
type: String, |
|||
default: uni.$u.props.alert.type |
|||
}, |
|||
// 辅助性文字
|
|||
description: { |
|||
type: String, |
|||
default: uni.$u.props.alert.description |
|||
}, |
|||
// 是否可关闭
|
|||
closable: { |
|||
type: Boolean, |
|||
default: uni.$u.props.alert.closable |
|||
}, |
|||
// 是否显示图标
|
|||
showIcon: { |
|||
type: Boolean, |
|||
default: uni.$u.props.alert.showIcon |
|||
}, |
|||
// 浅或深色调,light-浅色,dark-深色
|
|||
effect: { |
|||
type: String, |
|||
default: uni.$u.props.alert.effect |
|||
}, |
|||
// 文字是否居中
|
|||
center: { |
|||
type: Boolean, |
|||
default: uni.$u.props.alert.center |
|||
}, |
|||
// 字体大小
|
|||
fontSize: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.alert.fontSize |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,243 @@ |
|||
<template> |
|||
<u-transition |
|||
mode="fade" |
|||
:show="show" |
|||
> |
|||
<view |
|||
class="u-alert" |
|||
:class="[`u-alert--${type}--${effect}`]" |
|||
@tap.stop="clickHandler" |
|||
:style="[$u.addStyle(customStyle)]" |
|||
> |
|||
<view |
|||
class="u-alert__icon" |
|||
v-if="showIcon" |
|||
> |
|||
<u-icon |
|||
:name="iconName" |
|||
size="18" |
|||
:color="iconColor" |
|||
></u-icon> |
|||
</view> |
|||
<view |
|||
class="u-alert__content" |
|||
:style="[{ |
|||
paddingRight: closable ? '20px' : 0 |
|||
}]" |
|||
> |
|||
<text |
|||
class="u-alert__content__title" |
|||
v-if="title" |
|||
:style="[{ |
|||
fontSize: $u.addUnit(fontSize), |
|||
textAlign: center ? 'center' : 'left' |
|||
}]" |
|||
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]" |
|||
>{{ title }}</text> |
|||
<text |
|||
class="u-alert__content__desc" |
|||
v-if="description" |
|||
:style="[{ |
|||
fontSize: $u.addUnit(fontSize), |
|||
textAlign: center ? 'center' : 'left' |
|||
}]" |
|||
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]" |
|||
>{{ description }}</text> |
|||
</view> |
|||
<view |
|||
class="u-alert__close" |
|||
v-if="closable" |
|||
@tap.stop="closeHandler" |
|||
> |
|||
<u-icon |
|||
name="close" |
|||
:color="iconColor" |
|||
size="15" |
|||
></u-icon> |
|||
</view> |
|||
</view> |
|||
</u-transition> |
|||
</template> |
|||
|
|||
<script> |
|||
import props from './props.js'; |
|||
/** |
|||
* Alert 警告提示 |
|||
* @description 警告提示,展现需要关注的信息。 |
|||
* @tutorial https://www.uviewui.com/components/alertTips.html |
|||
* |
|||
* @property {String} title 显示的文字 |
|||
* @property {String} type 使用预设的颜色 (默认 'warning' ) |
|||
* @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选 |
|||
* @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false ) |
|||
* @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false ) |
|||
* @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' ) |
|||
* @property {Boolean} center 文字是否居中 (默认 false ) |
|||
* @property {String | Number} fontSize 字体大小 (默认 14 ) |
|||
* @property {Object} customStyle 定义需要用到的外部样式 |
|||
* @event {Function} click 点击组件时触发 |
|||
* @example <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert> |
|||
*/ |
|||
export default { |
|||
name: 'u-alert', |
|||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props], |
|||
data() { |
|||
return { |
|||
show: true |
|||
} |
|||
}, |
|||
computed: { |
|||
iconColor() { |
|||
return this.effect === 'light' ? this.type : '#fff' |
|||
}, |
|||
// 不同主题对应不同的图标 |
|||
iconName() { |
|||
switch (this.type) { |
|||
case 'success': |
|||
return 'checkmark-circle-fill'; |
|||
break; |
|||
case 'error': |
|||
return 'close-circle-fill'; |
|||
break; |
|||
case 'warning': |
|||
return 'error-circle-fill'; |
|||
break; |
|||
case 'info': |
|||
return 'info-circle-fill'; |
|||
break; |
|||
case 'primary': |
|||
return 'more-circle-fill'; |
|||
break; |
|||
default: |
|||
return 'error-circle-fill'; |
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
// 点击内容 |
|||
clickHandler() { |
|||
this.$emit('click') |
|||
}, |
|||
// 点击关闭按钮 |
|||
closeHandler() { |
|||
this.show = false |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "../../libs/css/components.scss"; |
|||
|
|||
.u-alert { |
|||
position: relative; |
|||
background-color: $u-primary; |
|||
padding: 8px 10px; |
|||
@include flex(row); |
|||
align-items: center; |
|||
border-top-left-radius: 4px; |
|||
border-top-right-radius: 4px; |
|||
border-bottom-left-radius: 4px; |
|||
border-bottom-right-radius: 4px; |
|||
|
|||
&--primary--dark { |
|||
background-color: $u-primary; |
|||
} |
|||
|
|||
&--primary--light { |
|||
background-color: #ecf5ff; |
|||
} |
|||
|
|||
&--error--dark { |
|||
background-color: $u-error; |
|||
} |
|||
|
|||
&--error--light { |
|||
background-color: #FEF0F0; |
|||
} |
|||
|
|||
&--success--dark { |
|||
background-color: $u-success; |
|||
} |
|||
|
|||
&--success--light { |
|||
background-color: #f5fff0; |
|||
} |
|||
|
|||
&--warning--dark { |
|||
background-color: $u-warning; |
|||
} |
|||
|
|||
&--warning--light { |
|||
background-color: #FDF6EC; |
|||
} |
|||
|
|||
&--info--dark { |
|||
background-color: $u-info; |
|||
} |
|||
|
|||
&--info--light { |
|||
background-color: #f4f4f5; |
|||
} |
|||
|
|||
&__icon { |
|||
margin-right: 5px; |
|||
} |
|||
|
|||
&__content { |
|||
@include flex(column); |
|||
flex: 1; |
|||
|
|||
&__title { |
|||
color: $u-main-color; |
|||
font-size: 14px; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
margin-bottom: 2px; |
|||
} |
|||
|
|||
&__desc { |
|||
color: $u-main-color; |
|||
font-size: 14px; |
|||
flex-wrap: wrap; |
|||
color: #fff; |
|||
} |
|||
} |
|||
|
|||
&__title--dark, |
|||
&__desc--dark { |
|||
color: #FFFFFF; |
|||
} |
|||
|
|||
&__text--primary--light, |
|||
&__text--primary--light { |
|||
color: $u-primary; |
|||
} |
|||
|
|||
&__text--success--light, |
|||
&__text--success--light { |
|||
color: $u-success; |
|||
} |
|||
|
|||
&__text--warning--light, |
|||
&__text--warning--light { |
|||
color: $u-warning; |
|||
} |
|||
|
|||
&__text--error--light, |
|||
&__text--error--light { |
|||
color: $u-error; |
|||
} |
|||
|
|||
&__text--info--light, |
|||
&__text--info--light { |
|||
color: $u-info; |
|||
} |
|||
|
|||
&__close { |
|||
position: absolute; |
|||
top: 11px; |
|||
right: 10px; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,46 @@ |
|||
export default { |
|||
props: { |
|||
// 头像图片组
|
|||
urls: { |
|||
type: Array, |
|||
default: uni.$u.props.avatarGroup.urls |
|||
}, |
|||
// 最多展示的头像数量
|
|||
maxCount: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.avatarGroup.maxCount |
|||
}, |
|||
// 头像形状
|
|||
shape: { |
|||
type: String, |
|||
default: uni.$u.props.avatarGroup.shape |
|||
}, |
|||
// 图片裁剪模式
|
|||
mode: { |
|||
type: String, |
|||
default: uni.$u.props.avatarGroup.mode |
|||
}, |
|||
// 超出maxCount时是否显示查看更多的提示
|
|||
showMore: { |
|||
type: Boolean, |
|||
default: uni.$u.props.avatarGroup.showMore |
|||
}, |
|||
// 头像大小
|
|||
size: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.avatarGroup.size |
|||
}, |
|||
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
|||
keyName: { |
|||
type: String, |
|||
default: uni.$u.props.avatarGroup.keyName |
|||
}, |
|||
gap: { |
|||
type: [String, Number], |
|||
validator(value) { |
|||
return value >= 0 && value <= 1 |
|||
}, |
|||
default: uni.$u.props.avatarGroup.gap |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,103 @@ |
|||
<template> |
|||
<view class="u-avatar-group"> |
|||
<view |
|||
class="u-avatar-group__item" |
|||
v-for="(item, index) in showUrl" |
|||
:key="index" |
|||
:style="{ |
|||
marginLeft: index === 0 ? 0 : $u.addUnit(-size * gap) |
|||
}" |
|||
> |
|||
<u-avatar |
|||
:size="size" |
|||
:shape="shape" |
|||
:mode="mode" |
|||
:src="$u.test.object(item) ? keyName && item[keyName] || item.url : item" |
|||
></u-avatar> |
|||
<view |
|||
class="u-avatar-group__item__show-more" |
|||
v-if="showMore && index === showUrl.length - 1 && urls.length > maxCount" |
|||
@tap="clickHandler" |
|||
> |
|||
<u--text |
|||
color="#ffffff" |
|||
:size="size * 0.4" |
|||
:text="`+${urls.length - showUrl.length}`" |
|||
align="center" |
|||
customStyle="justify-content: center" |
|||
></u--text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import props from './props.js'; |
|||
/** |
|||
* AvatarGroup 头像组 |
|||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 |
|||
* @tutorial https://www.uviewui.com/components/avatar.html |
|||
* |
|||
* @property {Array} urls 头像图片组 (默认 [] ) |
|||
* @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 ) |
|||
* @property {String} shape 头像形状( 'circle' (默认) | 'square' ) |
|||
* @property {String} mode 图片裁剪模式(默认 'scaleToFill' ) |
|||
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true ) |
|||
* @property {String | Number} size 头像大小 (默认 40 ) |
|||
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址 |
|||
* @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 ) |
|||
* |
|||
* @event {Function} showMore 头像组更多点击 |
|||
* @example <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=> |
|||
*/ |
|||
export default { |
|||
name: 'u-avatar-group', |
|||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props], |
|||
data() { |
|||
return { |
|||
|
|||
} |
|||
}, |
|||
computed: { |
|||
showUrl() { |
|||
return this.urls.slice(0, this.maxCount) |
|||
} |
|||
}, |
|||
methods: { |
|||
clickHandler() { |
|||
this.$emit('showMore') |
|||
} |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "../../libs/css/components.scss"; |
|||
|
|||
.u-avatar-group { |
|||
@include flex; |
|||
|
|||
&__item { |
|||
margin-left: -10px; |
|||
position: relative; |
|||
|
|||
&--no-indent { |
|||
// 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持 |
|||
margin-left: 0; |
|||
} |
|||
|
|||
&__show-more { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
background-color: rgba(0, 0, 0, 0.3); |
|||
@include flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
border-radius: 100px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,78 @@ |
|||
export default { |
|||
props: { |
|||
// 头像图片路径(不能为相对路径)
|
|||
src: { |
|||
type: String, |
|||
default: uni.$u.props.avatar.src |
|||
}, |
|||
// 头像形状,circle-圆形,square-方形
|
|||
shape: { |
|||
type: String, |
|||
default: uni.$u.props.avatar.shape |
|||
}, |
|||
// 头像尺寸
|
|||
size: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.avatar.size |
|||
}, |
|||
// 裁剪模式
|
|||
mode: { |
|||
type: String, |
|||
default: uni.$u.props.avatar.mode |
|||
}, |
|||
// 显示的文字
|
|||
text: { |
|||
type: String, |
|||
default: uni.$u.props.avatar.text |
|||
}, |
|||
// 背景色
|
|||
bgColor: { |
|||
type: String, |
|||
default: uni.$u.props.avatar.bgColor |
|||
}, |
|||
// 文字颜色
|
|||
color: { |
|||
type: String, |
|||
default: uni.$u.props.avatar.color |
|||
}, |
|||
// 文字大小
|
|||
fontSize: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.avatar.fontSize |
|||
}, |
|||
// 显示的图标
|
|||
icon: { |
|||
type: String, |
|||
default: uni.$u.props.avatar.icon |
|||
}, |
|||
// 显示小程序头像,只对百度,微信,QQ小程序有效
|
|||
mpAvatar: { |
|||
type: Boolean, |
|||
default: uni.$u.props.avatar.mpAvatar |
|||
}, |
|||
// 是否使用随机背景色
|
|||
randomBgColor: { |
|||
type: Boolean, |
|||
default: uni.$u.props.avatar.randomBgColor |
|||
}, |
|||
// 加载失败的默认头像(组件有内置默认图片)
|
|||
defaultUrl: { |
|||
type: String, |
|||
default: uni.$u.props.avatar.defaultUrl |
|||
}, |
|||
// 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
|
|||
colorIndex: { |
|||
type: [String, Number], |
|||
// 校验参数规则,索引在0-19之间
|
|||
validator(n) { |
|||
return uni.$u.test.range(n, [0, 19]) || n === '' |
|||
}, |
|||
default: uni.$u.props.avatar.colorIndex |
|||
}, |
|||
// 组件标识符
|
|||
name: { |
|||
type: String, |
|||
default: uni.$u.props.avatar.name |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,163 @@ |
|||
<template> |
|||
<view |
|||
class="u-avatar" |
|||
:class="[`u-avatar--${shape}`]" |
|||
:style="[{ |
|||
backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : $u.random(0, 19)] : bgColor) : 'transparent', |
|||
width: $u.addUnit(size), |
|||
height: $u.addUnit(size), |
|||
}, $u.addStyle(customStyle)]" |
|||
@tap.stop="clickHandler" |
|||
> |
|||
<slot> |
|||
<open-data |
|||
v-if="mpAvatar && allowMp" |
|||
type="userAvatarUrl" |
|||
:style="[{ |
|||
width: $u.addUnit(size), |
|||
height: $u.addUnit(size) |
|||
}]" |
|||
/> |
|||
<u-icon |
|||
v-else-if="icon" |
|||
:name="icon" |
|||
:size="fontSize" |
|||
:color="color" |
|||
></u-icon> |
|||
<u--text |
|||
v-else-if="text" |
|||
:text="text" |
|||
:size="fontSize" |
|||
:color="color" |
|||
align="center" |
|||
customStyle="justify-content: center" |
|||
></u--text> |
|||
<image |
|||
class="u-avatar__image" |
|||
v-else |
|||
:class="[`u-avatar__image--${shape}`]" |
|||
:src="avatarUrl" |
|||
:mode="mode" |
|||
@error="errorHandler" |
|||
:style="[{ |
|||
width: $u.addUnit(size), |
|||
height: $u.addUnit(size) |
|||
}]" |
|||
></image> |
|||
</slot> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import props from './props.js'; |
|||
const base64Avatar = |
|||
"data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z"; |
|||
/** |
|||
* Avatar 头像 |
|||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 |
|||
* @tutorial https://www.uviewui.com/components/avatar.html |
|||
* |
|||
* @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径) |
|||
* @property {String} shape 头像形状 ( circle (默认) | square) |
|||
* @property {String | Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值 (默认 40 ) |
|||
* @property {String} mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值 (默认 'scaleToFill' ) |
|||
* @property {String} text 用文字替代图片,级别优先于src |
|||
* @property {String} bgColor 背景颜色,一般显示文字时用 (默认 '#c0c4cc' ) |
|||
* @property {String} color 文字颜色 (默认 '#ffffff' ) |
|||
* @property {String | Number} fontSize 文字大小 (默认 18 ) |
|||
* @property {String} icon 显示的图标 |
|||
* @property {Boolean} mpAvatar 显示小程序头像,只对百度,微信,QQ小程序有效 (默认 false ) |
|||
* @property {Boolean} randomBgColor 是否使用随机背景色 (默认 false ) |
|||
* @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片) |
|||
* @property {String | Number} colorIndex 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间 |
|||
* @property {String} name 组件标识符 (默认 'level' ) |
|||
* @property {Object} customStyle 定义需要用到的外部样式 |
|||
* |
|||
* @event {Function} click 点击组件时触发 index: 用户传递的标识符 |
|||
* @example <u-avatar :src="src" mode="square"></u-avatar> |
|||
*/ |
|||
export default { |
|||
name: 'u-avatar', |
|||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props], |
|||
data() { |
|||
return { |
|||
// 如果配置randomBgColor参数为true,在图标或者文字的模式下,会随机从中取出一个颜色值当做背景色 |
|||
colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2', |
|||
'#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee', |
|||
'#73d1f1', |
|||
'#80a7dc' |
|||
], |
|||
avatarUrl: this.src, |
|||
allowMp: false |
|||
} |
|||
}, |
|||
watch: { |
|||
// 监听头像src的变化,赋值给内部的avatarUrl变量,因为图片加载失败时,需要修改图片的src为默认值 |
|||
// 而组件内部不能直接修改props的值,所以需要一个中间变量 |
|||
src: { |
|||
immediate: true, |
|||
handler(newVal) { |
|||
this.avatarUrl = newVal |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
imageStyle() { |
|||
const style = {} |
|||
return style |
|||
} |
|||
}, |
|||
created() { |
|||
this.init() |
|||
}, |
|||
methods: { |
|||
init() { |
|||
// 目前只有这几个小程序平台具有open-data标签 |
|||
// 其他平台可以通过uni.getUserInfo类似接口获取信息,但是需要弹窗授权(首次),不合符组件逻辑 |
|||
// 故目前自动获取小程序头像只支持这几个平台 |
|||
// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU |
|||
this.allowMp = true |
|||
// #endif |
|||
}, |
|||
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式 |
|||
isImg() { |
|||
return this.src.indexOf('/') !== -1 |
|||
}, |
|||
// 图片加载时失败时触发 |
|||
errorHandler() { |
|||
this.avatarUrl = this.defaultUrl || base64Avatar |
|||
}, |
|||
clickHandler() { |
|||
this.$emit('click', this.name) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "../../libs/css/components.scss"; |
|||
|
|||
.u-avatar { |
|||
@include flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
|
|||
&--circle { |
|||
border-radius: 100px; |
|||
} |
|||
|
|||
&--square { |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
&__image { |
|||
&--circle { |
|||
border-radius: 100px; |
|||
} |
|||
|
|||
&--square { |
|||
border-radius: 4px; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,54 @@ |
|||
export default { |
|||
props: { |
|||
// 返回顶部的形状,circle-圆形,square-方形
|
|||
mode: { |
|||
type: String, |
|||
default: uni.$u.props.backtop.mode |
|||
}, |
|||
// 自定义图标
|
|||
icon: { |
|||
type: String, |
|||
default: uni.$u.props.backtop.icon |
|||
}, |
|||
// 提示文字
|
|||
text: { |
|||
type: String, |
|||
default: uni.$u.props.backtop.text |
|||
}, |
|||
// 返回顶部滚动时间
|
|||
duration: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.backtop.duration |
|||
}, |
|||
// 滚动距离
|
|||
scrollTop: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.backtop.scrollTop |
|||
}, |
|||
// 距离顶部多少距离显示,单位px
|
|||
top: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.backtop.top |
|||
}, |
|||
// 返回顶部按钮到底部的距离,单位px
|
|||
bottom: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.backtop.bottom |
|||
}, |
|||
// 返回顶部按钮到右边的距离,单位px
|
|||
right: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.backtop.right |
|||
}, |
|||
// 层级
|
|||
zIndex: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.backtop.zIndex |
|||
}, |
|||
// 图标的样式,对象形式
|
|||
iconStyle: { |
|||
type: Object, |
|||
default: uni.$u.props.backtop.iconStyle |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,137 @@ |
|||
<template> |
|||
<u-transition |
|||
mode="fade" |
|||
:customStyle="backTopStyle" |
|||
:show="show" |
|||
> |
|||
<view |
|||
class="u-back-top" |
|||
:style="contentStyle" |
|||
v-if="!$slots.default && !$slots.$default" |
|||
@click="backToTop" |
|||
> |
|||
<u-icon |
|||
:name="icon" |
|||
:custom-style="iconStyle" |
|||
></u-icon> |
|||
<text |
|||
v-if="text" |
|||
class="u-back-top__text" |
|||
>{{text}}</text> |
|||
</view> |
|||
<slot v-else /> |
|||
</u-transition> |
|||
</template> |
|||
|
|||
<script> |
|||
import props from './props.js'; |
|||
// #ifdef APP-NVUE |
|||
const dom = weex.requireModule('dom') |
|||
// #endif |
|||
/** |
|||
* backTop 返回顶部 |
|||
* @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。 |
|||
* @tutorial https://uviewui.com/components/backTop.html |
|||
* |
|||
* @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' ) |
|||
* @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例 |
|||
* @property {String} text 提示文字 |
|||
* @property {String | Number} duration 返回顶部滚动时间 (默认 100) |
|||
* @property {String | Number} scrollTop 滚动距离 (默认 0 ) |
|||
* @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 ) |
|||
* @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 ) |
|||
* @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 ) |
|||
* @property {String | Number} zIndex 层级 (默认 9 ) |
|||
* @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'}) |
|||
* @property {Object} customStyle 定义需要用到的外部样式 |
|||
* |
|||
* @example <u-back-top :scrollTop="scrollTop"></u-back-top> |
|||
*/ |
|||
export default { |
|||
name: 'u-back-top', |
|||
mixins: [uni.$u.mpMixin, uni.$u.mixin,props], |
|||
computed: { |
|||
backTopStyle() { |
|||
// 动画组件样式 |
|||
const style = { |
|||
bottom: uni.$u.addUnit(this.bottom), |
|||
right: uni.$u.addUnit(this.right), |
|||
width: '40px', |
|||
height: '40px', |
|||
position: 'fixed', |
|||
zIndex: 10, |
|||
} |
|||
return style |
|||
}, |
|||
show() { |
|||
let top |
|||
// 如果是rpx,转为px |
|||
if (/rpx$/.test(this.top)) { |
|||
top = uni.rpx2px(parseInt(this.top)) |
|||
} else { |
|||
// 如果px,通过parseInt获取其数值部分 |
|||
top = parseInt(this.top) |
|||
} |
|||
return this.scrollTop > top |
|||
}, |
|||
contentStyle() { |
|||
const style = {} |
|||
let radius = 0 |
|||
// 是否圆形 |
|||
if(this.mode === 'circle') { |
|||
radius = '100px' |
|||
} else { |
|||
radius = '4px' |
|||
} |
|||
// 为了兼容安卓nvue,只能这么分开写 |
|||
style.borderTopLeftRadius = radius |
|||
style.borderTopRightRadius = radius |
|||
style.borderBottomLeftRadius = radius |
|||
style.borderBottomRightRadius = radius |
|||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle)) |
|||
} |
|||
}, |
|||
methods: { |
|||
backToTop() { |
|||
// #ifdef APP-NVUE |
|||
if (!this.$parent.$refs['u-back-top']) { |
|||
uni.$u.error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`) |
|||
} |
|||
dom.scrollToElement(this.$parent.$refs['u-back-top'], { |
|||
offset: 0 |
|||
}) |
|||
// #endif |
|||
|
|||
// #ifndef APP-NVUE |
|||
uni.pageScrollTo({ |
|||
scrollTop: 0, |
|||
duration: this.duration |
|||
}); |
|||
// #endif |
|||
this.$emit('click') |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import '../../libs/css/components.scss'; |
|||
$u-back-top-flex:1 !default; |
|||
$u-back-top-height:100% !default; |
|||
$u-back-top-background-color:#E1E1E1 !default; |
|||
$u-back-top-tips-font-size:12px !default; |
|||
.u-back-top { |
|||
@include flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
flex:$u-back-top-flex; |
|||
height: $u-back-top-height; |
|||
justify-content: center; |
|||
background-color: $u-back-top-background-color; |
|||
|
|||
&__tips { |
|||
font-size:$u-back-top-tips-font-size; |
|||
transform: scale(0.8); |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,72 @@ |
|||
export default { |
|||
props: { |
|||
// 是否显示圆点
|
|||
isDot: { |
|||
type: Boolean, |
|||
default: uni.$u.props.badge.isDot |
|||
}, |
|||
// 显示的内容
|
|||
value: { |
|||
type: [Number, String], |
|||
default: uni.$u.props.badge.value |
|||
}, |
|||
// 是否显示
|
|||
show: { |
|||
type: Boolean, |
|||
default: uni.$u.props.badge.show |
|||
}, |
|||
// 最大值,超过最大值会显示 '{max}+'
|
|||
max: { |
|||
type: [Number, String], |
|||
default: uni.$u.props.badge.max |
|||
}, |
|||
// 主题类型,error|warning|success|primary
|
|||
type: { |
|||
type: String, |
|||
default: uni.$u.props.badge.type |
|||
}, |
|||
// 当数值为 0 时,是否展示 Badge
|
|||
showZero: { |
|||
type: Boolean, |
|||
default: uni.$u.props.badge.showZero |
|||
}, |
|||
// 背景颜色,优先级比type高,如设置,type参数会失效
|
|||
bgColor: { |
|||
type: [String, null], |
|||
default: uni.$u.props.badge.bgColor |
|||
}, |
|||
// 字体颜色
|
|||
color: { |
|||
type: [String, null], |
|||
default: uni.$u.props.badge.color |
|||
}, |
|||
// 徽标形状,circle-四角均为圆角,horn-左下角为直角
|
|||
shape: { |
|||
type: String, |
|||
default: uni.$u.props.badge.shape |
|||
}, |
|||
// 设置数字的显示方式,overflow|ellipsis|limit
|
|||
// overflow会根据max字段判断,超出显示`${max}+`
|
|||
// ellipsis会根据max判断,超出显示`${max}...`
|
|||
// limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
|
|||
numberType: { |
|||
type: String, |
|||
default: uni.$u.props.badge.numberType |
|||
}, |
|||
// 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
|
|||
offset: { |
|||
type: Array, |
|||
default: uni.$u.props.badge.offset |
|||
}, |
|||
// 是否反转背景和字体颜色
|
|||
inverted: { |
|||
type: Boolean, |
|||
default: uni.$u.props.badge.inverted |
|||
}, |
|||
// 是否绝对定位
|
|||
absolute: { |
|||
type: Boolean, |
|||
default: uni.$u.props.badge.absolute |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,171 @@ |
|||
<template> |
|||
<text |
|||
v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)" |
|||
:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]" |
|||
:style="[$u.addStyle(customStyle), badgeStyle]" |
|||
class="u-badge" |
|||
>{{ isDot ? '' :showValue }}</text> |
|||
</template> |
|||
|
|||
<script> |
|||
import props from './props.js'; |
|||
/** |
|||
* badge 徽标数 |
|||
* @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。 |
|||
* @tutorial https://uviewui.com/components/badge.html |
|||
* |
|||
* @property {Boolean} isDot 是否显示圆点 (默认 false ) |
|||
* @property {String | Number} value 显示的内容 |
|||
* @property {Boolean} show 是否显示 (默认 true ) |
|||
* @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999) |
|||
* @property {String} type 主题类型,error|warning|success|primary (默认 'error' ) |
|||
* @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false ) |
|||
* @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效 |
|||
* @property {String} color 字体颜色 (默认 '#ffffff' ) |
|||
* @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' ) |
|||
* @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' ) |
|||
* @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效 |
|||
* @property {Boolean} inverted 是否反转背景和字体颜色(默认 false ) |
|||
* @property {Boolean} absolute 是否绝对定位(默认 false ) |
|||
* @property {Object} customStyle 定义需要用到的外部样式 |
|||
* @example <u-badge :type="type" :count="count"></u-badge> |
|||
*/ |
|||
export default { |
|||
name: 'u-badge', |
|||
mixins: [uni.$u.mpMixin, props, uni.$u.mixin], |
|||
computed: { |
|||
// 是否将badge中心与父组件右上角重合 |
|||
boxStyle() { |
|||
let style = {}; |
|||
return style; |
|||
}, |
|||
// 整个组件的样式 |
|||
badgeStyle() { |
|||
const style = {} |
|||
if(this.color) { |
|||
style.color = this.color |
|||
} |
|||
if (this.bgColor && !this.inverted) { |
|||
style.backgroundColor = this.bgColor |
|||
} |
|||
if (this.absolute) { |
|||
style.position = 'absolute' |
|||
// 如果有设置offset参数 |
|||
if(this.offset.length) { |
|||
// top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top |
|||
const top = this.offset[0] |
|||
const right = this.offset[1] || top |
|||
style.top = uni.$u.addUnit(top) |
|||
style.right = uni.$u.addUnit(right) |
|||
} |
|||
} |
|||
return style |
|||
}, |
|||
showValue() { |
|||
switch (this.numberType) { |
|||
case "overflow": |
|||
return Number(this.value) > Number(this.max) ? this.max + "+" : this.value |
|||
break; |
|||
case "ellipsis": |
|||
return Number(this.value) > Number(this.max) ? "..." : this.value |
|||
break; |
|||
case "limit": |
|||
return Number(this.value) > 999 ? Number(this.value) >= 9999 ? |
|||
Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value / |
|||
1e3 * 100) / 100 + "k" : this.value |
|||
break; |
|||
default: |
|||
return Number(this.value) |
|||
} |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "../../libs/css/components.scss"; |
|||
|
|||
$u-badge-primary: $u-primary !default; |
|||
$u-badge-error: $u-error !default; |
|||
$u-badge-success: $u-success !default; |
|||
$u-badge-info: $u-info !default; |
|||
$u-badge-warning: $u-warning !default; |
|||
$u-badge-dot-radius: 100px !default; |
|||
$u-badge-dot-size: 8px !default; |
|||
$u-badge-dot-right: 4px !default; |
|||
$u-badge-dot-top: 0 !default; |
|||
$u-badge-text-font-size: 11px !default; |
|||
$u-badge-text-right: 10px !default; |
|||
$u-badge-text-padding: 2px 5px !default; |
|||
$u-badge-text-align: center !default; |
|||
$u-badge-text-color: #FFFFFF !default; |
|||
|
|||
.u-badge { |
|||
border-top-right-radius: $u-badge-dot-radius; |
|||
border-top-left-radius: $u-badge-dot-radius; |
|||
border-bottom-left-radius: $u-badge-dot-radius; |
|||
border-bottom-right-radius: $u-badge-dot-radius; |
|||
@include flex; |
|||
line-height: $u-badge-text-font-size; |
|||
text-align: $u-badge-text-align; |
|||
font-size: $u-badge-text-font-size; |
|||
color: $u-badge-text-color; |
|||
|
|||
&--dot { |
|||
height: $u-badge-dot-size; |
|||
width: $u-badge-dot-size; |
|||
} |
|||
|
|||
&--inverted { |
|||
font-size: 13px; |
|||
} |
|||
|
|||
&--not-dot { |
|||
padding: $u-badge-text-padding; |
|||
} |
|||
|
|||
&--horn { |
|||
border-bottom-left-radius: 0; |
|||
} |
|||
|
|||
&--primary { |
|||
background-color: $u-badge-primary; |
|||
} |
|||
|
|||
&--primary--inverted { |
|||
color: $u-badge-primary; |
|||
} |
|||
|
|||
&--error { |
|||
background-color: $u-badge-error; |
|||
} |
|||
|
|||
&--error--inverted { |
|||
color: $u-badge-error; |
|||
} |
|||
|
|||
&--success { |
|||
background-color: $u-badge-success; |
|||
} |
|||
|
|||
&--success--inverted { |
|||
color: $u-badge-success; |
|||
} |
|||
|
|||
&--info { |
|||
background-color: $u-badge-info; |
|||
} |
|||
|
|||
&--info--inverted { |
|||
color: $u-badge-info; |
|||
} |
|||
|
|||
&--warning { |
|||
background-color: $u-badge-warning; |
|||
} |
|||
|
|||
&--warning--inverted { |
|||
color: $u-badge-warning; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,46 @@ |
|||
$u-button-active-opacity:0.75 !default; |
|||
$u-button-loading-text-margin-left:4px !default; |
|||
$u-button-text-color: #FFFFFF !default; |
|||
$u-button-text-plain-error-color:$u-error !default; |
|||
$u-button-text-plain-warning-color:$u-warning !default; |
|||
$u-button-text-plain-success-color:$u-success !default; |
|||
$u-button-text-plain-info-color:$u-info !default; |
|||
$u-button-text-plain-primary-color:$u-primary !default; |
|||
.u-button { |
|||
&--active { |
|||
opacity: $u-button-active-opacity; |
|||
} |
|||
|
|||
&--active--plain { |
|||
background-color: rgb(217, 217, 217); |
|||
} |
|||
|
|||
&__loading-text { |
|||
margin-left:$u-button-loading-text-margin-left; |
|||
} |
|||
|
|||
&__text, |
|||
&__loading-text { |
|||
color:$u-button-text-color; |
|||
} |
|||
|
|||
&__text--plain--error { |
|||
color:$u-button-text-plain-error-color; |
|||
} |
|||
|
|||
&__text--plain--warning { |
|||
color:$u-button-text-plain-warning-color; |
|||
} |
|||
|
|||
&__text--plain--success{ |
|||
color:$u-button-text-plain-success-color; |
|||
} |
|||
|
|||
&__text--plain--info { |
|||
color:$u-button-text-plain-info-color; |
|||
} |
|||
|
|||
&__text--plain--primary { |
|||
color:$u-button-text-plain-primary-color; |
|||
} |
|||
} |
@ -0,0 +1,156 @@ |
|||
/* |
|||
* @Author : LQ |
|||
* @Description : |
|||
* @version : 1.0 |
|||
* @Date : 2021-08-16 10:04:04 |
|||
* @LastAuthor : LQ |
|||
* @lastTime : 2021-08-16 10:04:24 |
|||
* @FilePath : /u-view2.0/uview-ui/components/u-button/props.js |
|||
*/ |
|||
export default { |
|||
props: { |
|||
// 是否细边框
|
|||
hairline: { |
|||
type: Boolean, |
|||
default: uni.$u.props.button.hairline |
|||
}, |
|||
// 按钮的预置样式,info,primary,error,warning,success
|
|||
type: { |
|||
type: String, |
|||
default: uni.$u.props.button.type |
|||
}, |
|||
// 按钮尺寸,large,normal,small,mini
|
|||
size: { |
|||
type: String, |
|||
default: uni.$u.props.button.size |
|||
}, |
|||
// 按钮形状,circle(两边为半圆),square(带圆角)
|
|||
shape: { |
|||
type: String, |
|||
default: uni.$u.props.button.shape |
|||
}, |
|||
// 按钮是否镂空
|
|||
plain: { |
|||
type: Boolean, |
|||
default: uni.$u.props.button.plain |
|||
}, |
|||
// 是否禁止状态
|
|||
disabled: { |
|||
type: Boolean, |
|||
default: uni.$u.props.button.disabled |
|||
}, |
|||
// 是否加载中
|
|||
loading: { |
|||
type: Boolean, |
|||
default: uni.$u.props.button.loading |
|||
}, |
|||
// 加载中提示文字
|
|||
loadingText: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.button.loadingText |
|||
}, |
|||
// 加载状态图标类型
|
|||
loadingMode: { |
|||
type: String, |
|||
default: uni.$u.props.button.loadingMode |
|||
}, |
|||
// 加载图标大小
|
|||
loadingSize: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.button.loadingSize |
|||
}, |
|||
// 开放能力,具体请看uniapp稳定关于button组件部分说明
|
|||
// https://uniapp.dcloud.io/component/button
|
|||
openType: { |
|||
type: String, |
|||
default: uni.$u.props.button.openType |
|||
}, |
|||
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
|||
// 取值为submit(提交表单),reset(重置表单)
|
|||
formType: { |
|||
type: String, |
|||
default: uni.$u.props.button.formType |
|||
}, |
|||
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
|
|||
// 只微信小程序、QQ小程序有效
|
|||
appParameter: { |
|||
type: String, |
|||
default: uni.$u.props.button.appParameter |
|||
}, |
|||
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
|
|||
hoverStopPropagation: { |
|||
type: Boolean, |
|||
default: uni.$u.props.button.hoverStopPropagation |
|||
}, |
|||
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
|
|||
lang: { |
|||
type: String, |
|||
default: uni.$u.props.button.lang |
|||
}, |
|||
// 会话来源,open-type="contact"时有效。只微信小程序有效
|
|||
sessionFrom: { |
|||
type: String, |
|||
default: uni.$u.props.button.sessionFrom |
|||
}, |
|||
// 会话内消息卡片标题,open-type="contact"时有效
|
|||
// 默认当前标题,只微信小程序有效
|
|||
sendMessageTitle: { |
|||
type: String, |
|||
default: uni.$u.props.button.sendMessageTitle |
|||
}, |
|||
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
|
|||
// 默认当前分享路径,只微信小程序有效
|
|||
sendMessagePath: { |
|||
type: String, |
|||
default: uni.$u.props.button.sendMessagePath |
|||
}, |
|||
// 会话内消息卡片图片,open-type="contact"时有效
|
|||
// 默认当前页面截图,只微信小程序有效
|
|||
sendMessageImg: { |
|||
type: String, |
|||
default: uni.$u.props.button.sendMessageImg |
|||
}, |
|||
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
|
|||
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
|
|||
showMessageCard: { |
|||
type: Boolean, |
|||
default: uni.$u.props.button.showMessageCard |
|||
}, |
|||
// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
|||
dataName: { |
|||
type: String, |
|||
default: uni.$u.props.button.dataName |
|||
}, |
|||
// 节流,一定时间内只能触发一次
|
|||
throttleTime: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.button.throttleTime |
|||
}, |
|||
// 按住后多久出现点击态,单位毫秒
|
|||
hoverStartTime: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.button.hoverStartTime |
|||
}, |
|||
// 手指松开后点击态保留时间,单位毫秒
|
|||
hoverStayTime: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.button.hoverStayTime |
|||
}, |
|||
// 按钮文字,之所以通过props传入,是因为slot传入的话
|
|||
// nvue中无法控制文字的样式
|
|||
text: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.button.text |
|||
}, |
|||
// 按钮图标
|
|||
icon: { |
|||
type: String, |
|||
default: uni.$u.props.button.icon |
|||
}, |
|||
// 按钮颜色,支持传入linear-gradient渐变色
|
|||
color: { |
|||
type: String, |
|||
default: uni.$u.props.button.color |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,485 @@ |
|||
<template> |
|||
<!-- #ifndef APP-NVUE --> |
|||
<button |
|||
:hover-start-time="Number(hoverStartTime)" |
|||
:hover-stay-time="Number(hoverStayTime)" |
|||
:form-type="formType" |
|||
:open-type="openType" |
|||
:app-parameter="appParameter" |
|||
:hover-stop-propagation="hoverStopPropagation" |
|||
:send-message-title="sendMessageTitle" |
|||
send-message-path="sendMessagePath" |
|||
:lang="lang" |
|||
:data-name="dataName" |
|||
:session-from="sessionFrom" |
|||
:send-message-img="sendMessageImg" |
|||
:show-message-card="showMessageCard" |
|||
@getphonenumber="getphonenumber" |
|||
@getuserinfo="getuserinfo" |
|||
@error="error" |
|||
@opensetting="opensetting" |
|||
@launchapp="launchapp" |
|||
:hover-class="!disabled && !loading ? 'u-button--active' : ''" |
|||
class="u-button u-reset-button" |
|||
:style="[baseColor, $u.addStyle(customStyle)]" |
|||
@tap="clickHandler" |
|||
:class="bemClass" |
|||
> |
|||
<template v-if="loading"> |
|||
<u-loading-icon |
|||
:mode="loadingMode" |
|||
:size="textSize * 1.15" |
|||
:color="loadingColor" |
|||
></u-loading-icon> |
|||
<text |
|||
class="u-button__loading-text" |
|||
:style="[{ fontSize: textSize + 'px' }]" |
|||
>{{ loadingText || text }}</text |
|||
> |
|||
</template> |
|||
<template v-else> |
|||
<u-icon |
|||
v-if="icon" |
|||
:name="icon" |
|||
:color="iconColor" |
|||
:size="textSize * 1.35" |
|||
:customStyle="{ marginRight: '2px' }" |
|||
></u-icon> |
|||
<slot> |
|||
<text |
|||
class="u-button__text" |
|||
:style="[{ fontSize: textSize + 'px' }]" |
|||
>{{ text }}</text |
|||
> |
|||
</slot> |
|||
</template> |
|||
</button> |
|||
<!-- #endif --> |
|||
|
|||
<!-- #ifdef APP-NVUE --> |
|||
<view |
|||
:hover-start-time="Number(hoverStartTime)" |
|||
:hover-stay-time="Number(hoverStayTime)" |
|||
class="u-button" |
|||
:hover-class=" |
|||
!disabled && !loading && !color && (plain || type === 'info') |
|||
? 'u-button--active--plain' |
|||
: !disabled && !loading && !plain |
|||
? 'u-button--active' |
|||
: '' |
|||
" |
|||
@tap="clickHandler" |
|||
:class="bemClass" |
|||
:style="[baseColor, $u.addStyle(customStyle)]" |
|||
> |
|||
<template v-if="loading"> |
|||
<u-loading-icon |
|||
:mode="loadingMode" |
|||
:size="textSize * 1.15" |
|||
:color="loadingColor" |
|||
></u-loading-icon> |
|||
<text |
|||
class="u-button__loading-text" |
|||
:style="[nvueTextStyle]" |
|||
:class="[plain && `u-button__text--plain--${type}`]" |
|||
>{{ loadingText || text }}</text |
|||
> |
|||
</template> |
|||
<template v-else> |
|||
<u-icon |
|||
v-if="icon" |
|||
:name="icon" |
|||
:color="iconColor" |
|||
:size="textSize * 1.35" |
|||
></u-icon> |
|||
<text |
|||
class="u-button__text" |
|||
:style="[ |
|||
{ |
|||
marginLeft: icon ? '2px' : 0, |
|||
}, |
|||
nvueTextStyle, |
|||
]" |
|||
:class="[plain && `u-button__text--plain--${type}`]" |
|||
>{{ text }}</text |
|||
> |
|||
</template> |
|||
</view> |
|||
<!-- #endif --> |
|||
</template> |
|||
|
|||
<script> |
|||
import button from "../../libs/mixin/button.js"; |
|||
import openType from "../../libs/mixin/openType.js"; |
|||
import props from "./props.js"; |
|||
/** |
|||
* button 按钮 |
|||
* @description Button 按钮 |
|||
* @tutorial https://www.uviewui.com/components/button.html |
|||
* |
|||
* @property {Boolean} hairline 是否显示按钮的细边框 (默认 true ) |
|||
* @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' ) |
|||
* @property {String} size 按钮尺寸,large,normal,mini (默认 normal) |
|||
* @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' ) |
|||
* @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false) |
|||
* @property {Boolean} disabled 是否禁用 (默认 false) |
|||
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false) |
|||
* @property {String | Number} loadingText 加载中提示文字 |
|||
* @property {String} loadingMode 加载状态图标类型 (默认 'spinner' ) |
|||
* @property {String | Number} loadingSize 加载图标大小 (默认 15 ) |
|||
* @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明 |
|||
* @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 |
|||
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效) |
|||
* @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true ) |
|||
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en ) |
|||
* @property {String} sessionFrom 会话来源,openType="contact"时有效 |
|||
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效 |
|||
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效 |
|||
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效 |
|||
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false) |
|||
* @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 |
|||
* @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 ) |
|||
* @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 ) |
|||
* @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 ) |
|||
* @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式) |
|||
* @property {String} icon 按钮图标 |
|||
* @property {String} color 按钮颜色,支持传入linear-gradient渐变色 |
|||
* @property {Object} customStyle 定义需要用到的外部样式 |
|||
* |
|||
* @event {Function} click 非禁止并且非加载中,才能点击 |
|||
* @event {Function} getphonenumber open-type="getPhoneNumber"时有效 |
|||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo |
|||
* @event {Function} error 当使用开放能力时,发生错误的回调 |
|||
* @event {Function} opensetting 在打开授权设置页并关闭后回调 |
|||
* @event {Function} launchapp 打开 APP 成功的回调 |
|||
* @example <u-button>月落</u-button> |
|||
*/ |
|||
export default { |
|||
name: "u-button", |
|||
// #ifdef MP |
|||
mixins: [uni.$u.mpMixin, uni.$u.mixin, button, openType, props], |
|||
// #endif |
|||
// #ifndef MP |
|||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props], |
|||
// #endif |
|||
data() { |
|||
return {}; |
|||
}, |
|||
computed: { |
|||
// 生成bem风格的类名 |
|||
bemClass() { |
|||
// this.bem为一个computed变量,在mixin中 |
|||
if (!this.color) { |
|||
return this.bem( |
|||
"button", |
|||
["type", "shape", "size"], |
|||
["disabled", "plain", "hairline"] |
|||
); |
|||
} else { |
|||
// 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式 |
|||
return this.bem( |
|||
"button", |
|||
["shape", "size"], |
|||
["disabled", "plain", "hairline"] |
|||
); |
|||
} |
|||
}, |
|||
loadingColor() { |
|||
if (this.plain) { |
|||
// 如果有设置color值,则用color值,否则使用type主题颜色 |
|||
return this.color |
|||
? this.color |
|||
: this.$u.config.color[`u-${this.type}`]; |
|||
} |
|||
if (this.type === "info") { |
|||
return "#c9c9c9"; |
|||
} |
|||
return "rgb(200, 200, 200)"; |
|||
}, |
|||
iconColor() { |
|||
// 如果是镂空状态,设置了color就用color值,否则使用主题颜色, |
|||
// u-icon的color能接受一个主题颜色的值 |
|||
if (this.plain) { |
|||
return this.color ? this.color : this.type; |
|||
} else { |
|||
return "#ffffff"; |
|||
} |
|||
}, |
|||
baseColor() { |
|||
let style = {}; |
|||
if (this.color) { |
|||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色 |
|||
style.color = this.plain ? this.color : "white"; |
|||
if (!this.plain) { |
|||
// 非镂空,背景色使用自定义的颜色 |
|||
style["background-color"] = this.color; |
|||
} |
|||
if (this.color.indexOf("gradient") !== -1) { |
|||
// 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色 |
|||
// weex文档说明可以写borderWidth的形式,为什么这里需要分开写? |
|||
// 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效 |
|||
style.borderTopWidth = 0; |
|||
style.borderRightWidth = 0; |
|||
style.borderBottomWidth = 0; |
|||
style.borderLeftWidth = 0; |
|||
if (!this.plain) { |
|||
style.backgroundImage = this.color; |
|||
} |
|||
} else { |
|||
// 非渐变色,则设置边框相关的属性 |
|||
style.borderColor = this.color; |
|||
style.borderWidth = "1px"; |
|||
style.borderStyle = "solid"; |
|||
} |
|||
} |
|||
return style; |
|||
}, |
|||
// nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置 |
|||
nvueTextStyle() { |
|||
let style = {}; |
|||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色 |
|||
if (this.type === "info") { |
|||
style.color = "#323233"; |
|||
} |
|||
if (this.color) { |
|||
style.color = this.plain ? this.color : "white"; |
|||
} |
|||
style.fontSize = this.textSize + "px"; |
|||
return style; |
|||
}, |
|||
// 字体大小 |
|||
textSize() { |
|||
let fontSize = 14, |
|||
{ size } = this; |
|||
if (size === "large") fontSize = 16; |
|||
if (size === "normal") fontSize = 14; |
|||
if (size === "small") fontSize = 12; |
|||
if (size === "mini") fontSize = 10; |
|||
return fontSize; |
|||
}, |
|||
}, |
|||
methods: { |
|||
clickHandler() { |
|||
// 非禁止并且非加载中,才能点击 |
|||
if (!this.disabled && !this.loading) { |
|||
this.$emit("click"); |
|||
} |
|||
}, |
|||
// 下面为对接uniapp官方按钮开放能力事件回调的对接 |
|||
getphonenumber(res) { |
|||
this.$emit("getphonenumber", res); |
|||
}, |
|||
getuserinfo(res) { |
|||
this.$emit("getuserinfo", res); |
|||
}, |
|||
error(res) { |
|||
this.$emit("error", res); |
|||
}, |
|||
opensetting(res) { |
|||
this.$emit("opensetting", res); |
|||
}, |
|||
launchapp(res) { |
|||
this.$emit("launchapp", res); |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "../../libs/css/components.scss"; |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
@import "./vue.scss"; |
|||
/* #endif */ |
|||
|
|||
/* #ifdef APP-NVUE */ |
|||
@import "./nvue.scss"; |
|||
/* #endif */ |
|||
|
|||
$u-button-u-button-height: 40px !default; |
|||
$u-button-text-font-size: 15px !default; |
|||
$u-button-loading-text-font-size: 15px !default; |
|||
$u-button-loading-text-margin-left: 4px !default; |
|||
$u-button-large-width: 100% !default; |
|||
$u-button-large-height: 50px !default; |
|||
$u-button-normal-padding: 0 12px !default; |
|||
$u-button-large-padding: 0 15px !default; |
|||
$u-button-normal-font-size: 14px !default; |
|||
$u-button-small-min-width: 60px !default; |
|||
$u-button-small-height: 30px !default; |
|||
$u-button-small-padding: 0px 8px !default; |
|||
$u-button-mini-padding: 0px 8px !default; |
|||
$u-button-small-font-size: 12px !default; |
|||
$u-button-mini-height: 22px !default; |
|||
$u-button-mini-font-size: 10px !default; |
|||
$u-button-mini-min-width: 50px !default; |
|||
$u-button-disabled-opacity: 0.5 !default; |
|||
$u-button-info-color: #323233 !default; |
|||
$u-button-info-background-color: #fff !default; |
|||
$u-button-info-border-color: #ebedf0 !default; |
|||
$u-button-info-border-width: 1px !default; |
|||
$u-button-info-border-style: solid !default; |
|||
$u-button-success-color: #fff !default; |
|||
$u-button-success-background-color: $u-success !default; |
|||
$u-button-success-border-color: $u-button-success-background-color !default; |
|||
$u-button-success-border-width: 1px !default; |
|||
$u-button-success-border-style: solid !default; |
|||
$u-button-primary-color: #fff !default; |
|||
$u-button-primary-background-color: $u-primary !default; |
|||
$u-button-primary-border-color: $u-button-primary-background-color !default; |
|||
$u-button-primary-border-width: 1px !default; |
|||
$u-button-primary-border-style: solid !default; |
|||
$u-button-error-color: #fff !default; |
|||
$u-button-error-background-color: $u-error !default; |
|||
$u-button-error-border-color: $u-button-error-background-color !default; |
|||
$u-button-error-border-width: 1px !default; |
|||
$u-button-error-border-style: solid !default; |
|||
$u-button-warning-color: #fff !default; |
|||
$u-button-warning-background-color: $u-warning !default; |
|||
$u-button-warning-border-color: $u-button-warning-background-color !default; |
|||
$u-button-warning-border-width: 1px !default; |
|||
$u-button-warning-border-style: solid !default; |
|||
$u-button-block-width: 100% !default; |
|||
$u-button-circle-border-top-right-radius: 100px !default; |
|||
$u-button-circle-border-top-left-radius: 100px !default; |
|||
$u-button-circle-border-bottom-left-radius: 100px !default; |
|||
$u-button-circle-border-bottom-right-radius: 100px !default; |
|||
$u-button-square-border-top-right-radius: 3px !default; |
|||
$u-button-square-border-top-left-radius: 3px !default; |
|||
$u-button-square-border-bottom-left-radius: 3px !default; |
|||
$u-button-square-border-bottom-right-radius: 3px !default; |
|||
$u-button-icon-min-width: 1em !default; |
|||
$u-button-plain-background-color: #fff !default; |
|||
$u-button-hairline-border-width: 0.5px !default; |
|||
|
|||
.u-button { |
|||
height: $u-button-u-button-height; |
|||
position: relative; |
|||
align-items: center; |
|||
justify-content: center; |
|||
@include flex; |
|||
/* #ifndef APP-NVUE */ |
|||
box-sizing: border-box; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
|
|||
&__text { |
|||
font-size: $u-button-text-font-size; |
|||
} |
|||
|
|||
&__loading-text { |
|||
font-size: $u-button-loading-text-font-size; |
|||
margin-left: $u-button-loading-text-margin-left; |
|||
} |
|||
|
|||
&--large { |
|||
/* #ifndef APP-NVUE */ |
|||
width: $u-button-large-width; |
|||
/* #endif */ |
|||
height: $u-button-large-height; |
|||
padding: $u-button-large-padding; |
|||
} |
|||
|
|||
&--normal { |
|||
padding: $u-button-normal-padding; |
|||
font-size: $u-button-normal-font-size; |
|||
} |
|||
|
|||
&--small { |
|||
/* #ifndef APP-NVUE */ |
|||
min-width: $u-button-small-min-width; |
|||
/* #endif */ |
|||
height: $u-button-small-height; |
|||
padding: $u-button-small-padding; |
|||
font-size: $u-button-small-font-size; |
|||
} |
|||
|
|||
&--mini { |
|||
height: $u-button-mini-height; |
|||
font-size: $u-button-mini-font-size; |
|||
/* #ifndef APP-NVUE */ |
|||
min-width: $u-button-mini-min-width; |
|||
/* #endif */ |
|||
padding: $u-button-mini-padding; |
|||
} |
|||
|
|||
&--disabled { |
|||
opacity: $u-button-disabled-opacity; |
|||
} |
|||
|
|||
&--info { |
|||
color: $u-button-info-color; |
|||
background-color: $u-button-info-background-color; |
|||
border-color: $u-button-info-border-color; |
|||
border-width: $u-button-info-border-width; |
|||
border-style: $u-button-info-border-style; |
|||
} |
|||
|
|||
&--success { |
|||
color: $u-button-success-color; |
|||
background-color: $u-button-success-background-color; |
|||
border-color: $u-button-success-border-color; |
|||
border-width: $u-button-success-border-width; |
|||
border-style: $u-button-success-border-style; |
|||
} |
|||
|
|||
&--primary { |
|||
color: $u-button-primary-color; |
|||
background-color: $u-button-primary-background-color; |
|||
border-color: $u-button-primary-border-color; |
|||
border-width: $u-button-primary-border-width; |
|||
border-style: $u-button-primary-border-style; |
|||
} |
|||
|
|||
&--error { |
|||
color: $u-button-error-color; |
|||
background-color: $u-button-error-background-color; |
|||
border-color: $u-button-error-border-color; |
|||
border-width: $u-button-error-border-width; |
|||
border-style: $u-button-error-border-style; |
|||
} |
|||
|
|||
&--warning { |
|||
color: $u-button-warning-color; |
|||
background-color: $u-button-warning-background-color; |
|||
border-color: $u-button-warning-border-color; |
|||
border-width: $u-button-warning-border-width; |
|||
border-style: $u-button-warning-border-style; |
|||
} |
|||
|
|||
&--block { |
|||
@include flex; |
|||
width: $u-button-block-width; |
|||
} |
|||
|
|||
&--circle { |
|||
border-top-right-radius: $u-button-circle-border-top-right-radius; |
|||
border-top-left-radius: $u-button-circle-border-top-left-radius; |
|||
border-bottom-left-radius: $u-button-circle-border-bottom-left-radius; |
|||
border-bottom-right-radius: $u-button-circle-border-bottom-right-radius; |
|||
} |
|||
|
|||
&--square { |
|||
border-bottom-left-radius: $u-button-square-border-top-right-radius; |
|||
border-bottom-right-radius: $u-button-square-border-top-left-radius; |
|||
border-top-left-radius: $u-button-square-border-bottom-left-radius; |
|||
border-top-right-radius: $u-button-square-border-bottom-right-radius; |
|||
} |
|||
|
|||
&__icon { |
|||
/* #ifndef APP-NVUE */ |
|||
min-width: $u-button-icon-min-width; |
|||
line-height: inherit !important; |
|||
vertical-align: top; |
|||
/* #endif */ |
|||
} |
|||
|
|||
&--plain { |
|||
background-color: $u-button-plain-background-color; |
|||
} |
|||
|
|||
&--hairline { |
|||
border-width: $u-button-hairline-border-width !important; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,73 @@ |
|||
// nvue下hover-class无效 |
|||
$u-button-before-top:50% !default; |
|||
$u-button-before-left:50% !default; |
|||
$u-button-before-width:100% !default; |
|||
$u-button-before-height:100% !default; |
|||
$u-button-before-transform:translate(-50%, -50%) !default; |
|||
$u-button-before-opacity:0 !default; |
|||
$u-button-before-background-color:#000 !default; |
|||
$u-button-before-border-color:#000 !default; |
|||
$u-button-active-before-opacity:.15 !default; |
|||
$u-button-icon-margin-left:4px !default; |
|||
$u-button-plain-u-button-info-color:$u-info; |
|||
$u-button-plain-u-button-success-color:$u-success; |
|||
$u-button-plain-u-button-error-color:$u-error; |
|||
$u-button-plain-u-button-warning-color:$u-error; |
|||
|
|||
.u-button { |
|||
&:before { |
|||
position: absolute; |
|||
top:$u-button-before-top; |
|||
left:$u-button-before-left; |
|||
width:$u-button-before-width; |
|||
height:$u-button-before-height; |
|||
border: inherit; |
|||
border-radius: inherit; |
|||
transform:$u-button-before-transform; |
|||
opacity:$u-button-before-opacity; |
|||
content: " "; |
|||
background-color:$u-button-before-background-color; |
|||
border-color:$u-button-before-border-color; |
|||
} |
|||
|
|||
&--active { |
|||
&:before { |
|||
opacity: .15 |
|||
} |
|||
} |
|||
|
|||
&__icon+&__text:not(:empty), |
|||
&__loading-text { |
|||
margin-left:$u-button-icon-margin-left; |
|||
} |
|||
|
|||
&--plain { |
|||
&.u-button--primary { |
|||
color: $u-primary; |
|||
} |
|||
} |
|||
|
|||
&--plain { |
|||
&.u-button--info { |
|||
color:$u-button-plain-u-button-info-color; |
|||
} |
|||
} |
|||
|
|||
&--plain { |
|||
&.u-button--success { |
|||
color:$u-button-plain-u-button-success-color; |
|||
} |
|||
} |
|||
|
|||
&--plain { |
|||
&.u-button--error { |
|||
color:$u-button-plain-u-button-error-color; |
|||
} |
|||
} |
|||
|
|||
&--plain { |
|||
&.u-button--warning { |
|||
color:$u-button-plain-u-button-warning-color; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,99 @@ |
|||
<template> |
|||
<view class="u-calendar-header u-border-bottom"> |
|||
<text |
|||
class="u-calendar-header__title" |
|||
v-if="showTitle" |
|||
>{{ title }}</text> |
|||
<text |
|||
class="u-calendar-header__subtitle" |
|||
v-if="showSubtitle" |
|||
>{{ subtitle }}</text> |
|||
<view class="u-calendar-header__weekdays"> |
|||
<text class="u-calendar-header__weekdays__weekday">一</text> |
|||
<text class="u-calendar-header__weekdays__weekday">二</text> |
|||
<text class="u-calendar-header__weekdays__weekday">三</text> |
|||
<text class="u-calendar-header__weekdays__weekday">四</text> |
|||
<text class="u-calendar-header__weekdays__weekday">五</text> |
|||
<text class="u-calendar-header__weekdays__weekday">六</text> |
|||
<text class="u-calendar-header__weekdays__weekday">日</text> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'u-calendar-header', |
|||
mixins: [uni.$u.mpMixin, uni.$u.mixin], |
|||
props: { |
|||
// 标题 |
|||
title: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
// 副标题 |
|||
subtitle: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
// 是否显示标题 |
|||
showTitle: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
// 是否显示副标题 |
|||
showSubtitle: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
}, |
|||
data() { |
|||
return { |
|||
|
|||
} |
|||
}, |
|||
methods: { |
|||
name() { |
|||
|
|||
} |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "../../libs/css/components.scss"; |
|||
|
|||
.u-calendar-header { |
|||
padding-bottom: 4px; |
|||
|
|||
&__title { |
|||
font-size: 16px; |
|||
color: $u-main-color; |
|||
text-align: center; |
|||
height: 42px; |
|||
line-height: 42px; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
&__subtitle { |
|||
font-size: 14px; |
|||
color: $u-main-color; |
|||
height: 40px; |
|||
text-align: center; |
|||
line-height: 40px; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
&__weekdays { |
|||
@include flex; |
|||
justify-content: space-between; |
|||
|
|||
&__weekday { |
|||
font-size: 13px; |
|||
color: $u-main-color; |
|||
line-height: 30px; |
|||
flex: 1; |
|||
text-align: center; |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,570 @@ |
|||
<template> |
|||
<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper"> |
|||
<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]" |
|||
:ref="`u-calendar-month-${index}`" :id="`month-${item.month}`"> |
|||
<text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text> |
|||
<view class="u-calendar-month__days"> |
|||
<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper"> |
|||
<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text> |
|||
</view> |
|||
<view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1" |
|||
:style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)" |
|||
:class="[item1.selected && 'u-calendar-month__days__day__select--selected']"> |
|||
<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]"> |
|||
<text class="u-calendar-month__days__day__select__info" |
|||
:class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']" |
|||
:style="[textStyle(item1)]">{{ item1.day }}</text> |
|||
<text v-if="getBottomInfo(index, index1, item1)" |
|||
class="u-calendar-month__days__day__select__buttom-info" |
|||
:class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']" |
|||
:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text> |
|||
<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// #ifdef APP-NVUE |
|||
// 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度 |
|||
const dom = uni.requireNativePlugin('dom') |
|||
// #endif |
|||
import dayjs from '../../libs/util/dayjs.js'; |
|||
export default { |
|||
name: 'u-calendar-month', |
|||
mixins: [uni.$u.mpMixin, uni.$u.mixin], |
|||
props: { |
|||
// 是否显示月份背景色 |
|||
showMark: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
// 主题色,对底部按钮和选中日期有效 |
|||
color: { |
|||
type: String, |
|||
default: '#3c9cff' |
|||
}, |
|||
// 月份数据 |
|||
months: { |
|||
type: Array, |
|||
default: () => [] |
|||
}, |
|||
// 日期选择类型 |
|||
mode: { |
|||
type: String, |
|||
default: 'single' |
|||
}, |
|||
// 日期行高 |
|||
rowHeight: { |
|||
type: [String, Number], |
|||
default: 58 |
|||
}, |
|||
// mode=multiple时,最多可选多少个日期 |
|||
maxCount: { |
|||
type: [String, Number], |
|||
default: Infinity |
|||
}, |
|||
// mode=range时,第一个日期底部的提示文字 |
|||
startText: { |
|||
type: String, |
|||
default: '开始' |
|||
}, |
|||
// mode=range时,最后一个日期底部的提示文字 |
|||
endText: { |
|||
type: String, |
|||
default: '结束' |
|||
}, |
|||
// 默认选中的日期,mode为multiple或range是必须为数组格式 |
|||
defaultDate: { |
|||
type: [Array, String, Date], |
|||
default: null |
|||
}, |
|||
// 最小的可选日期 |
|||
minDate: { |
|||
type: [String, Number], |
|||
default: 0 |
|||
}, |
|||
// 最大可选日期 |
|||
maxDate: { |
|||
type: [String, Number], |
|||
default: 0 |
|||
}, |
|||
// 如果没有设置maxDate,则往后推多少个月 |
|||
maxMonth: { |
|||
type: [String, Number], |
|||
default: 2 |
|||
}, |
|||
// 是否为只读状态,只读状态下禁止选择日期 |
|||
readonly: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.readonly |
|||
}, |
|||
// 日期区间最多可选天数,默认无限制,mode = range时有效 |
|||
maxRange: { |
|||
type: [Number, String], |
|||
default: Infinity |
|||
}, |
|||
// 范围选择超过最多可选天数时的提示文案,mode = range时有效 |
|||
rangePrompt: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 |
|||
showRangePrompt: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
// 是否允许日期范围的起止时间为同一天,mode = range时有效 |
|||
allowSameDay: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
// 每个日期的宽度 |
|||
width: 0, |
|||
// 当前选中的日期item |
|||
item: {}, |
|||
selected: [] |
|||
} |
|||
}, |
|||
watch: { |
|||
selectedChange: { |
|||
immediate: true, |
|||
handler(n) { |
|||
this.setDefaultDate() |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
// 多个条件的变化,会引起选中日期的变化,这里统一管理监听 |
|||
selectedChange() { |
|||
return [this.minDate, this.maxDate, this.defaultDate] |
|||
}, |
|||
dayStyle(index1, index2, item) { |
|||
return (index1, index2, item) => { |
|||
const style = {} |
|||
let week = item.week |
|||
// 不进行四舍五入的形式保留2位小数 |
|||
const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1)) |
|||
// 得出每个日期的宽度 |
|||
style.width = uni.$u.addUnit(dayWidth) |
|||
style.height = uni.$u.addUnit(this.rowHeight) |
|||
if (index2 === 0) { |
|||
// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数 |
|||
week = (week === 0 ? 7 : week) - 1 |
|||
style.marginLeft = uni.$u.addUnit(week * dayWidth) |
|||
} |
|||
if (this.mode === 'range') { |
|||
// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug |
|||
style.paddingLeft = 0 |
|||
style.paddingRight = 0 |
|||
style.paddingBottom = 0 |
|||
style.paddingTop = 0 |
|||
} |
|||
return style |
|||
} |
|||
}, |
|||
daySelectStyle() { |
|||
return (index1, index2, item) => { |
|||
let date = dayjs(item.date).format("YYYY-MM-DD"), |
|||
style = {} |
|||
// 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断 |
|||
if (this.selected.some(item => this.dateSame(item, date))) { |
|||
style.backgroundColor = this.color |
|||
} |
|||
if (this.mode === 'single') { |
|||
if (date === this.selected[0]) { |
|||
// 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等 |
|||
style.borderTopLeftRadius = '3px' |
|||
style.borderBottomLeftRadius = '3px' |
|||
style.borderTopRightRadius = '3px' |
|||
style.borderBottomRightRadius = '3px' |
|||
} |
|||
} else if (this.mode === 'range') { |
|||
if (this.selected.length >= 2) { |
|||
const len = this.selected.length - 1 |
|||
// 第一个日期设置左上角和左下角的圆角 |
|||
if (this.dateSame(date, this.selected[0])) { |
|||
style.borderTopLeftRadius = '3px' |
|||
style.borderBottomLeftRadius = '3px' |
|||
} |
|||
// 最后一个日期设置右上角和右下角的圆角 |
|||
if (this.dateSame(date, this.selected[len])) { |
|||
style.borderTopRightRadius = '3px' |
|||
style.borderBottomRightRadius = '3px' |
|||
} |
|||
// 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值 |
|||
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this |
|||
.selected[len]))) { |
|||
style.backgroundColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[90] |
|||
// 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符 |
|||
style.opacity = 0.7 |
|||
} |
|||
} else if (this.selected.length === 1) { |
|||
// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug |
|||
// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现 |
|||
style.borderTopLeftRadius = '3px' |
|||
style.borderBottomLeftRadius = '3px' |
|||
} |
|||
} else { |
|||
if (this.selected.some(item => this.dateSame(item, date))) { |
|||
style.borderTopLeftRadius = '3px' |
|||
style.borderBottomLeftRadius = '3px' |
|||
style.borderTopRightRadius = '3px' |
|||
style.borderBottomRightRadius = '3px' |
|||
} |
|||
} |
|||
return style |
|||
} |
|||
}, |
|||
// 某个日期是否被选中 |
|||
textStyle() { |
|||
return (item) => { |
|||
const date = dayjs(item.date).format("YYYY-MM-DD"), |
|||
style = {} |
|||
// 选中的日期,提示文字设置白色 |
|||
if (this.selected.some(item => this.dateSame(item, date))) { |
|||
style.color = '#ffffff' |
|||
} |
|||
if (this.mode === 'range') { |
|||
const len = this.selected.length - 1 |
|||
// 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色 |
|||
if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this |
|||
.selected[len]))) { |
|||
style.color = this.color |
|||
} |
|||
} |
|||
return style |
|||
} |
|||
}, |
|||
// 获取底部的提示文字 |
|||
getBottomInfo() { |
|||
return (index1, index2, item) => { |
|||
const date = dayjs(item.date).format("YYYY-MM-DD") |
|||
const bottomInfo = item.bottomInfo |
|||
// 当为日期范围模式时,且选择的日期个数大于0时 |
|||
if (this.mode === 'range' && this.selected.length > 0) { |
|||
if (this.selected.length === 1) { |
|||
// 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始” |
|||
if (this.dateSame(date, this.selected[0])) return this.startText |
|||
else return bottomInfo |
|||
} else { |
|||
const len = this.selected.length - 1 |
|||
// 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期 |
|||
if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) && |
|||
len === 1) { |
|||
// 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中 |
|||
return `${this.startText}/${this.endText}` |
|||
} else if (this.dateSame(date, this.selected[0])) { |
|||
return this.startText |
|||
} else if (this.dateSame(date, this.selected[len])) { |
|||
return this.endText |
|||
} else { |
|||
return bottomInfo |
|||
} |
|||
} |
|||
} else { |
|||
return bottomInfo |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.init() |
|||
}, |
|||
methods: { |
|||
init() { |
|||
this.$nextTick(() => { |
|||
// 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度 |
|||
// 因为nvue下,$nextTick并不是100%可靠的 |
|||
uni.$u.sleep(10).then(() => { |
|||
this.getWrapperWidth() |
|||
this.getMonthRect() |
|||
}) |
|||
}) |
|||
}, |
|||
// 判断两个日期是否相等 |
|||
dateSame(date1, date2) { |
|||
return dayjs(date1).isSame(dayjs(date2)) |
|||
}, |
|||
// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度 |
|||
getWrapperWidth() { |
|||
// #ifdef APP-NVUE |
|||
dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => { |
|||
this.width = res.size.width |
|||
}) |
|||
// #endif |
|||
// #ifndef APP-NVUE |
|||
this.$uGetRect('.u-calendar-month-wrapper').then(size => { |
|||
this.width = size.width |
|||
}) |
|||
// #endif |
|||
}, |
|||
getMonthRect() { |
|||
// 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份 |
|||
const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise( |
|||
`u-calendar-month-${index}`)) |
|||
// 一次性返回 |
|||
Promise.all(promiseAllArr).then( |
|||
sizes => { |
|||
let height = 1 |
|||
const topArr = [] |
|||
for (let i = 0; i < this.months.length; i++) { |
|||
// 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份 |
|||
topArr[i] = height |
|||
height += sizes[i].height |
|||
} |
|||
// 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出 |
|||
this.$emit('updateMonthTop', topArr) |
|||
}) |
|||
}, |
|||
// 获取每个月份区域的尺寸 |
|||
getMonthRectByPromise(el) { |
|||
// #ifndef APP-NVUE |
|||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html |
|||
// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同 |
|||
return new Promise(resolve => { |
|||
this.$uGetRect(`.${el}`).then(size => { |
|||
resolve(size) |
|||
}) |
|||
}) |
|||
// #endif |
|||
|
|||
// #ifdef APP-NVUE |
|||
// nvue下,使用dom模块查询元素高度 |
|||
// 返回一个promise,让调用此方法的主体能使用then回调 |
|||
return new Promise(resolve => { |
|||
dom.getComponentRect(this.$refs[el][0], res => { |
|||
resolve(res.size) |
|||
}) |
|||
}) |
|||
// #endif |
|||
}, |
|||
// 点击某一个日期 |
|||
clickHandler(index1, index2, item) { |
|||
if (this.readonly) { |
|||
return; |
|||
} |
|||
this.item = item |
|||
const date = dayjs(item.date).format("YYYY-MM-DD") |
|||
if (item.disabled) return |
|||
// 对上一次选择的日期数组进行深度克隆 |
|||
let selected = uni.$u.deepClone(this.selected) |
|||
if (this.mode === 'single') { |
|||
// 单选情况下,让数组中的元素为当前点击的日期 |
|||
selected = [date] |
|||
} else if (this.mode === 'multiple') { |
|||
if (selected.some(item => this.dateSame(item, date))) { |
|||
// 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果 |
|||
const itemIndex = selected.findIndex(item => item === date) |
|||
selected.splice(itemIndex, 1) |
|||
} else { |
|||
// 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去 |
|||
if (selected.length < this.maxCount) selected.push(date) |
|||
} |
|||
} else { |
|||
// 选择区间形式 |
|||
if (selected.length === 0 || selected.length >= 2) { |
|||
// 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期 |
|||
selected = [date] |
|||
} else if (selected.length === 1) { |
|||
// 如果已经选择了开始日期 |
|||
const existsDate = selected[0] |
|||
// 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期 |
|||
if (dayjs(date).isBefore(existsDate)) { |
|||
selected = [date] |
|||
} else if (dayjs(date).isAfter(existsDate)) { |
|||
// 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示 |
|||
if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) { |
|||
if(this.rangePrompt) { |
|||
uni.$u.toast(this.rangePrompt) |
|||
} else { |
|||
uni.$u.toast(`选择天数不能超过 ${this.maxRange} 天`) |
|||
} |
|||
return |
|||
} |
|||
// 如果当前日期大于已有日期,将当前的添加到数组尾部 |
|||
selected.push(date) |
|||
const startDate = selected[0] |
|||
const endDate = selected[1] |
|||
const arr = [] |
|||
let i = 0 |
|||
do { |
|||
// 将开始和结束日期之间的日期添加到数组中 |
|||
arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD")) |
|||
i++ |
|||
// 累加的日期小于结束日期时,继续下一次的循环 |
|||
} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate))) |
|||
// 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来 |
|||
arr.push(endDate) |
|||
selected = arr |
|||
} else { |
|||
// 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己 |
|||
if (selected[0] === date && !this.allowSameDay) return |
|||
selected.push(date) |
|||
} |
|||
} |
|||
} |
|||
this.setSelected(selected) |
|||
}, |
|||
// 设置默认日期 |
|||
setDefaultDate() { |
|||
if (!this.defaultDate) { |
|||
// 如果没有设置默认日期,则将当天日期设置为默认选中的日期 |
|||
const selected = [dayjs().format("YYYY-MM-DD")] |
|||
return this.setSelected(selected, false) |
|||
} |
|||
let defaultDate = [] |
|||
const minDate = this.minDate || dayjs().format("YYYY-MM-DD") |
|||
const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD") |
|||
if (this.mode === 'single') { |
|||
// 单选模式,可以是字符串或数组,Date对象等 |
|||
if (!uni.$u.test.array(this.defaultDate)) { |
|||
defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")] |
|||
} else { |
|||
defaultDate = [this.defaultDate[0]] |
|||
} |
|||
} else { |
|||
// 如果为非数组,则不执行 |
|||
if (!uni.$u.test.array(this.defaultDate)) return |
|||
defaultDate = this.defaultDate |
|||
} |
|||
// 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素 |
|||
defaultDate = defaultDate.filter(item => { |
|||
return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs( |
|||
maxDate).add(1, 'day')) |
|||
}) |
|||
this.setSelected(defaultDate, false) |
|||
}, |
|||
setSelected(selected, event = true) { |
|||
this.selected = selected |
|||
event && this.$emit('monthSelected', this.selected) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "../../libs/css/components.scss"; |
|||
|
|||
.u-calendar-month-wrapper { |
|||
margin-top: 4px; |
|||
} |
|||
|
|||
.u-calendar-month { |
|||
|
|||
&__title { |
|||
font-size: 14px; |
|||
line-height: 42px; |
|||
height: 42px; |
|||
color: $u-main-color; |
|||
text-align: center; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
&__days { |
|||
position: relative; |
|||
@include flex; |
|||
flex-wrap: wrap; |
|||
|
|||
&__month-mark-wrapper { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
@include flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
|
|||
&__text { |
|||
font-size: 155px; |
|||
color: rgba(231, 232, 234, 0.83); |
|||
} |
|||
} |
|||
|
|||
&__day { |
|||
@include flex; |
|||
padding: 2px; |
|||
|
|||
&__select { |
|||
flex: 1; |
|||
@include flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
position: relative; |
|||
|
|||
&__dot { |
|||
width: 7px; |
|||
height: 7px; |
|||
border-radius: 100px; |
|||
background-color: $u-error; |
|||
position: absolute; |
|||
top: 12px; |
|||
right: 7px; |
|||
} |
|||
|
|||
&__buttom-info { |
|||
color: $u-content-color; |
|||
text-align: center; |
|||
position: absolute; |
|||
bottom: 5px; |
|||
font-size: 10px; |
|||
text-align: center; |
|||
left: 0; |
|||
right: 0; |
|||
|
|||
&--selected { |
|||
color: #ffffff; |
|||
} |
|||
|
|||
&--disabled { |
|||
color: #cacbcd; |
|||
} |
|||
} |
|||
|
|||
&__info { |
|||
text-align: center; |
|||
font-size: 16px; |
|||
|
|||
&--selected { |
|||
color: #ffffff; |
|||
} |
|||
|
|||
&--disabled { |
|||
color: #cacbcd; |
|||
} |
|||
} |
|||
|
|||
&--selected { |
|||
background-color: $u-primary; |
|||
@include flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
flex: 1; |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
&--range-selected { |
|||
opacity: 0.3; |
|||
border-radius: 0; |
|||
} |
|||
|
|||
&--range-start-selected { |
|||
border-top-right-radius: 0; |
|||
border-bottom-right-radius: 0; |
|||
} |
|||
|
|||
&--range-end-selected { |
|||
border-top-left-radius: 0; |
|||
border-bottom-left-radius: 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,134 @@ |
|||
export default { |
|||
props: { |
|||
// 日历顶部标题
|
|||
title: { |
|||
type: String, |
|||
default: uni.$u.props.calendar.title |
|||
}, |
|||
// 是否显示标题
|
|||
showTitle: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.showTitle |
|||
}, |
|||
// 是否显示副标题
|
|||
showSubtitle: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.showSubtitle |
|||
}, |
|||
// 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
|
|||
mode: { |
|||
type: String, |
|||
default: uni.$u.props.calendar.mode |
|||
}, |
|||
// mode=range时,第一个日期底部的提示文字
|
|||
startText: { |
|||
type: String, |
|||
default: uni.$u.props.calendar.startText |
|||
}, |
|||
// mode=range时,最后一个日期底部的提示文字
|
|||
endText: { |
|||
type: String, |
|||
default: uni.$u.props.calendar.endText |
|||
}, |
|||
// 自定义列表
|
|||
customList: { |
|||
type: Array, |
|||
default: uni.$u.props.calendar.customList |
|||
}, |
|||
// 主题色,对底部按钮和选中日期有效
|
|||
color: { |
|||
type: String, |
|||
default: uni.$u.props.calendar.color |
|||
}, |
|||
// 最小的可选日期
|
|||
minDate: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.calendar.minDate |
|||
}, |
|||
// 最大可选日期
|
|||
maxDate: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.calendar.maxDate |
|||
}, |
|||
// 默认选中的日期,mode为multiple或range是必须为数组格式
|
|||
defaultDate: { |
|||
type: [Array, String, Date, null], |
|||
default: uni.$u.props.calendar.defaultDate |
|||
}, |
|||
// mode=multiple时,最多可选多少个日期
|
|||
maxCount: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.calendar.maxCount |
|||
}, |
|||
// 日期行高
|
|||
rowHeight: { |
|||
type: [String, Number], |
|||
default: uni.$u.props.calendar.rowHeight |
|||
}, |
|||
// 日期格式化函数
|
|||
formatter: { |
|||
type: [Function, null], |
|||
default: uni.$u.props.calendar.formatter |
|||
}, |
|||
// 是否显示农历
|
|||
showLunar: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.showLunar |
|||
}, |
|||
// 是否显示月份背景色
|
|||
showMark: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.showMark |
|||
}, |
|||
// 确定按钮的文字
|
|||
confirmText: { |
|||
type: String, |
|||
default: uni.$u.props.calendar.confirmText |
|||
}, |
|||
// 确认按钮处于禁用状态时的文字
|
|||
confirmDisabledText: { |
|||
type: String, |
|||
default: uni.$u.props.calendar.confirmDisabledText |
|||
}, |
|||
// 是否显示日历弹窗
|
|||
show: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.show |
|||
}, |
|||
// 是否允许点击遮罩关闭日历
|
|||
closeOnClickOverlay: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.closeOnClickOverlay |
|||
}, |
|||
// 是否为只读状态,只读状态下禁止选择日期
|
|||
readonly: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.readonly |
|||
}, |
|||
// 是否展示确认按钮
|
|||
showConfirm: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.showConfirm |
|||
}, |
|||
// 日期区间最多可选天数,默认无限制,mode = range时有效
|
|||
maxRange: { |
|||
type: [Number, String], |
|||
default: uni.$u.props.calendar.maxRange |
|||
}, |
|||
// 范围选择超过最多可选天数时的提示文案,mode = range时有效
|
|||
rangePrompt: { |
|||
type: String, |
|||
default: uni.$u.props.calendar.rangePrompt |
|||
}, |
|||
// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
|
|||
showRangePrompt: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.showRangePrompt |
|||
}, |
|||
// 是否允许日期范围的起止时间为同一天,mode = range时有效
|
|||
allowSameDay: { |
|||
type: Boolean, |
|||
default: uni.$u.props.calendar.allowSameDay |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,288 @@ |
|||
<template> |
|||
<u-popup |
|||
:show="show" |
|||
mode="bottom" |
|||
closeable |
|||
@close="close" |
|||
round |
|||
:closeOnClickOverlay="closeOnClickOverlay" |
|||
> |
|||
<view class="u-calendar"> |
|||
<uHeader |
|||
:title="title" |
|||
:subtitle="subtitle" |
|||
:showSubtitle="showSubtitle" |
|||
:showTitle="showTitle" |
|||
></uHeader> |
|||
<scroll-view |
|||
:style="{ |
|||
height: $u.addUnit(listHeight) |
|||
}" |
|||
scroll-y |
|||
@scroll="onScroll" |
|||
:scrollIntoView="scrollIntoView" |
|||
> |
|||
<uMonth |
|||
:color="color" |
|||
:rowHeight="rowHeight" |
|||
:showMark="showMark" |
|||
:months="months" |
|||
:mode="mode" |
|||
:maxCount="maxCount" |
|||
:startText="startText" |
|||
:endText="endText" |
|||
:defaultDate="defaultDate" |
|||
:minDate="minDate" |
|||
:maxDate="maxDate" |
|||
:maxMonth="maxMonth" |
|||
:readonly="readonly" |
|||
:maxRange="maxRange" |
|||
:rangePrompt="rangePrompt" |
|||
:showRangePrompt="showRangePrompt" |
|||
:allowSameDay="allowSameDay" |
|||
ref="month" |
|||
@monthSelected="monthSelected" |
|||
@updateMonthTop="updateMonthTop" |
|||
></uMonth> |
|||
</scroll-view> |
|||
<slot name="footer" v-if="showConfirm"> |
|||
<view class="u-calendar__confirm"> |
|||
<u-button |
|||
shape="circle" |
|||
:text="buttonDisabled ? confirmDisabledText : confirmText" |
|||
:color="color" |
|||
@click="confirm" |
|||
:disabled="buttonDisabled" |
|||
></u-button> |
|||
</view> |
|||
</slot> |
|||
</view> |
|||
</u-popup> |
|||
</template> |
|||
|
|||
<script> |
|||
import uHeader from './header.vue'; |
|||
import uMonth from './month.vue'; |
|||
import props from './props.js'; |
|||
import util from './util.js'; |
|||
import dayjs from '../../libs/util/dayjs.js'; |
|||
import Calendar from '../../libs/util/calendar.js'; |
|||
/** |
|||
* Calendar 日历 |
|||
* @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中. |
|||
* @tutorial https://www.uviewui.com/components/calendar.html |
|||
* |
|||
* @property {String} title 标题内容 (默认 日期选择 ) |
|||
* @property {Boolean} showTitle 是否显示标题 (默认 true ) |
|||
* @property {Boolean} showSubtitle 是否显示副标题 (默认 true ) |
|||
* @property {String} mode 日期类型选择 single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' ) |
|||
* @property {String} startText mode=range时,第一个日期底部的提示文字 (默认 '开始' ) |
|||
* @property {String} endText mode=range时,最后一个日期底部的提示文字 (默认 '结束' ) |
|||
* @property {Array} customList 自定义列表 |
|||
* @property {String} color 主题色,对底部按钮和选中日期有效 (默认 ‘#3c9cff' ) |
|||
* @property {String | Number} minDate 最小的可选日期 (默认 0 ) |
|||
* @property {String | Number} maxDate 最大可选日期 (默认 0 ) |
|||
* @property {Array | String| Date} defaultDate 默认选中的日期,mode为multiple或range是必须为数组格式 |
|||
* @property {String | Number} maxCount mode=multiple时,最多可选多少个日期 (默认 Number.MAX_SAFE_INTEGER ) |
|||
* @property {String | Number} rowHeight 日期行高 (默认 56 ) |
|||
* @property {Function} formatter 日期格式化函数 |
|||
* @property {Boolean} showLunar 是否显示农历 (默认 false ) |
|||
* @property {Boolean} showMark 是否显示月份背景色 (默认 true ) |
|||
* @property {String} confirmText 确定按钮的文字 (默认 '确定' ) |
|||
* @property {String} confirmDisabledText 确认按钮处于禁用状态时的文字 (默认 '确定' ) |
|||
* @property {Boolean} show 是否显示日历弹窗 (默认 false ) |
|||
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭日历 (默认 false ) |
|||
* @property {Boolean} readonly 是否为只读状态,只读状态下禁止选择日期 (默认 false ) |
|||
* @property {String | Number} maxRange 日期区间最多可选天数,默认无限制,mode = range时有效 |
|||
* @property {String} rangePrompt 范围选择超过最多可选天数时的提示文案,mode = range时有效 |
|||
* @property {Boolean} showRangePrompt 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true ) |
|||
* @property {Boolean} allowSameDay 是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false ) |
|||
* |
|||
* @event {Function()} confirm 点击确定按钮时触发 选择日期相关的返回参数 |
|||
* @event {Function()} close 日历关闭时触发 可定义页面关闭时的回调事件 |
|||
* @example <u-calendar :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm"> |
|||
</u-calendar> |
|||
* */ |
|||
export default { |
|||
name: 'u-calendar', |
|||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props], |
|||
components: { |
|||
uHeader, |
|||
uMonth |
|||
}, |
|||
data() { |
|||
return { |
|||
// 需要显示的月份的数组 |
|||
months: [], |
|||
// 在月份滚动区域中,当前视图中月份的index索引 |
|||
monthIndex: 0, |
|||
// 月份滚动区域的高度 |
|||
listHeight: 0, |
|||
// month组件中选择的日期数组 |
|||
selected: [], |
|||
// 如果没有设置最大可选日期,默认为往后推3个月 |
|||
maxMonth: 3, |
|||
scrollIntoView: '', |
|||
// 过滤处理方法 |
|||
innerFormatter: value => value |
|||
} |
|||
}, |
|||
watch: { |
|||
selectedChange: { |
|||
immediate: true, |
|||
handler(n) { |
|||
this.setMonth() |
|||
} |
|||
}, |
|||
// 打开弹窗时,设置月份数据 |
|||
show: { |
|||
immediate: true, |
|||
handler(n) { |
|||
this.setMonth() |
|||
} |
|||
}, |
|||
}, |
|||
computed: { |
|||
// 多个条件的变化,会引起选中日期的变化,这里统一管理监听 |
|||
selectedChange() { |
|||
return [this.minDate, this.maxDate, this.defaultDate] |
|||
}, |
|||
subtitle() { |
|||
// 初始化时,this.months为空数组,所以需要特别判断处理 |
|||
if (this.months.length) { |
|||
return `${this.months[this.monthIndex].year}年${this.months[this.monthIndex].month}月` |
|||
} else { |
|||
return '' |
|||
} |
|||
}, |
|||
buttonDisabled() { |
|||
// 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态 |
|||
if (this.mode === 'range') { |
|||
if (this.selected.length <= 1) { |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
} else { |
|||
return false |
|||
} |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.start = Date.now() |
|||
this.init() |
|||
}, |
|||
methods: { |
|||
// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用 |
|||
setFormatter(e) { |
|||
this.innerFormatter = e |
|||
}, |
|||
// month组件内部选择日期后,通过事件通知给父组件 |
|||
monthSelected(e) { |
|||
this.selected = e |
|||
if(!this.showConfirm) { |
|||
// 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还 |
|||
if (this.mode === 'multiple' || this.mode === 'single' || this.mode === 'range' && this.selected.length >= 2) { |
|||
this.$emit('confirm', this.selected) |
|||
} |
|||
} |
|||
}, |
|||
init() { |
|||
// 滚动区域的高度 |
|||
this.listHeight = this.rowHeight * 5 + 30 |
|||
this.setMonth() |
|||
}, |
|||
close() { |
|||
this.$emit('close') |
|||
}, |
|||
// 点击确定按钮 |
|||
confirm() { |
|||
if (!this.buttonDisabled) { |
|||
this.$emit('confirm', this.selected) |
|||
} |
|||
}, |
|||
// 设置月份数据 |
|||
setMonth() { |
|||
// 最小日期的毫秒数 |
|||
const minDate = this.minDate || dayjs().valueOf() |
|||
// 如果没有指定最大日期,则往后推3个月 |
|||
const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').valueOf() |
|||
// 最小与最大月份 |
|||
let minMonth = dayjs(minDate).month() + 1 |
|||
let maxMonth = dayjs(maxDate).month() + 1 |
|||
// 如果maxMonth小于minMonth,则意味着maxMonth为下一年的月份,需要加上12,为的是计算出两个月份之间间隔着多少月份 |
|||
maxMonth = minMonth > maxMonth ? maxMonth + 12 : maxMonth |
|||
// 最大最小月份之间的共有多少个月份 |
|||
const months = Math.abs(minMonth - maxMonth) |
|||
// 先清空数组 |
|||
this.months = [] |
|||
for (let i = 0; i <= months; i++) { |
|||
this.months.push({ |
|||
date: new Array(dayjs(minDate).add(i, 'month').daysInMonth()).fill(1).map((item, |
|||
index) => { |
|||
// 日期,取值1-31 |
|||
let day = index + 1 |
|||
// 星期,0-6,0为周日 |
|||
const week = dayjs(minDate).add(i, "month").date(day).day() |
|||
const date = dayjs(minDate).add(i, "month").date(day).format("YYYY-MM-DD") |
|||
let bottomInfo = '' |
|||
if (this.showLunar) { |
|||
// 将日期转为农历格式 |
|||
const lunar = Calendar.solar2lunar(dayjs(date).year(), dayjs(date) |
|||
.month() + 1, dayjs(date).date()) |
|||
bottomInfo = lunar.IDayCn |
|||
} |
|||
let config = { |
|||
day, |
|||
week, |
|||
// 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态 |
|||
disabled: dayjs(date).isBefore(dayjs(minDate).format("YYYY-MM-DD")) || |
|||
dayjs(date).isAfter(dayjs(maxDate).format("YYYY-MM-DD")), |
|||
// 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理 |
|||
date: new Date(date), |
|||
bottomInfo, |
|||
dot: false, |
|||
month: dayjs(minDate).add(i, "month").month() + 1 |
|||
} |
|||
const formatter = this.formatter || this.innerFormatter |
|||
return formatter(config) |
|||
}), |
|||
// 当前所属的月份 |
|||
month: dayjs(minDate).add(i, "month").month() + 1, |
|||
// 当前年份 |
|||
year: dayjs(minDate).add(i, "month").year() |
|||
}); |
|||
} |
|||
}, |
|||
// scroll-view滚动监听 |
|||
onScroll(event) { |
|||
// 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值 |
|||
const scrollTop = Math.max(0, event.detail.scrollTop) |
|||
// 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引 |
|||
for (let i = 0; i < this.months.length; i++) { |
|||
if (scrollTop >= (this.months[i].top || this.listHeight)) { |
|||
this.monthIndex = i |
|||
} |
|||
} |
|||
}, |
|||
// 更新月份的top值 |
|||
updateMonthTop(topArr = []) { |
|||
// 设置对应月份的top值,用于onScroll方法更新月份 |
|||
topArr.map((item, index) => { |
|||
this.months[index].top = item |
|||
}) |
|||
} |
|||
}, |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
@import "../../libs/css/components.scss"; |
|||
|
|||
.u-calendar { |
|||
|
|||
&__confirm { |
|||
padding: 7px 18px; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,85 @@ |
|||
export default { |
|||
methods: { |
|||
// 设置月份数据
|
|||
setMonth() { |
|||
// 月初是周几
|
|||
const day = dayjs(this.date).date(1).day() |
|||
const start = day == 0 ? 6 : day - 1 |
|||
|
|||
// 本月天数
|
|||
const days = dayjs(this.date).endOf('month').format('D') |
|||
|
|||
// 上个月天数
|
|||
const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D') |
|||
|
|||
// 日期数据
|
|||
const arr = [] |
|||
// 清空表格
|
|||
this.month = [] |
|||
|
|||
// 添加上月数据
|
|||
arr.push( |
|||
...new Array(start).fill(1).map((e, i) => { |
|||
const day = prevDays - start + i + 1 |
|||
|
|||
return { |
|||
value: day, |
|||
disabled: true, |
|||
date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD') |
|||
} |
|||
}) |
|||
) |
|||
|
|||
// 添加本月数据
|
|||
arr.push( |
|||
...new Array(days - 0).fill(1).map((e, i) => { |
|||
const day = i + 1 |
|||
|
|||
return { |
|||
value: day, |
|||
date: dayjs(this.date).date(day).format('YYYY-MM-DD') |
|||
} |
|||
}) |
|||
) |
|||
|
|||
// 添加下个月
|
|||
arr.push( |
|||
...new Array(42 - days - start).fill(1).map((e, i) => { |
|||
const day = i + 1 |
|||
|
|||
return { |
|||
value: day, |
|||
disabled: true, |
|||
date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD') |
|||
} |
|||
}) |
|||
) |
|||
|
|||
// 分割数组
|
|||
for (let n = 0; n < arr.length; n += 7) { |
|||
this.month.push( |
|||
arr.slice(n, n + 7).map((e, i) => { |
|||
e.index = i + n |
|||
|
|||
// 自定义信息
|
|||
const custom = this.customList.find((c) => c.date == e.date) |
|||
|
|||
// 农历
|
|||
if (this.lunar) { |
|||
const { |
|||
IDayCn, |
|||
IMonthCn |
|||
} = this.getLunar(e.date) |
|||
e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn |
|||
} |
|||
|
|||
return { |
|||
...e, |
|||
...custom |
|||
} |
|||
}) |
|||
) |
|||
} |
|||
} |
|||
} |
|||
} |