Browse Source

init

master
zmt 3 years ago
commit
85d1e7efbf
  1. 25
      .gitignore
  2. 21
      App.vue
  3. 128
      README.md
  4. 256
      components/nx-search/nx-search.vue
  5. 33
      main.js
  6. 85
      manifest.json
  7. 30
      nxTemp/apis/userAPI.js
  8. 30
      nxTemp/config/index.config.js
  9. 278
      nxTemp/config/requestConfig.js
  10. 11
      nxTemp/filter/index.js
  11. 26
      nxTemp/index.js
  12. 129
      nxTemp/request/core/request.js
  13. 84
      nxTemp/request/core/utils.js
  14. 7
      nxTemp/request/index.js
  15. 313
      nxTemp/request/request.md
  16. 169
      nxTemp/request/upload/qiniuUploader.js
  17. 214
      nxTemp/request/upload/upload.js
  18. 234
      nxTemp/request/upload/utils.js
  19. 73
      nxTemp/router/index.js
  20. 1
      nxTemp/router/uni-simple-router.js
  21. 19
      nxTemp/store/index.js
  22. 67
      nxTemp/store/modules/user.js
  23. 165
      nxTemp/utils/tools.js
  24. 35
      nxTemp/wechat/wechat.js
  25. 16
      package-lock.json
  26. 6
      package.json
  27. 152
      pages.json
  28. 327
      pages/event/event_grade.vue
  29. 196
      pages/event/event_list.vue
  30. 118
      pages/index/index.vue
  31. 27
      pages/login/agreement.vue
  32. 255
      pages/login/forget.vue
  33. 242
      pages/login/login.vue
  34. 303
      pages/login/reg.vue
  35. 30
      pages/me/index.vue
  36. 74
      pages/message/center_list.vue
  37. 69
      pages/message/detail.vue
  38. 13
      pages/public/404.vue
  39. 184
      static/colorui/animation.css
  40. 69
      static/colorui/components/cu-custom.vue
  41. 1226
      static/colorui/icon.css
  42. 3912
      static/colorui/main.css
  43. BIN
      static/images/event/enent_none.png
  44. BIN
      static/images/event/event_close.png
  45. BIN
      static/images/event/event_location.png
  46. BIN
      static/images/event/event_lock.png
  47. BIN
      static/images/index/arrow_right.png
  48. BIN
      static/images/index/index_bell.png
  49. BIN
      static/images/index/index_bg.png
  50. BIN
      static/images/index/index_cup.png
  51. BIN
      static/images/index/index_logout.png
  52. BIN
      static/images/login/agree_circle.png
  53. BIN
      static/images/login/agreed_circle.png
  54. BIN
      static/images/login/ty0.png
  55. BIN
      static/images/login/ty1.png
  56. BIN
      static/images/logo_main.png
  57. BIN
      static/images/tabbar/tab_home_01.png
  58. BIN
      static/images/tabbar/tab_home_02.png
  59. BIN
      static/images/tabbar/tab_user_01.png
  60. BIN
      static/images/tabbar/tab_user_02.png
  61. 131
      static/style/base.scss
  62. 164
      static/style/mixin/flex.scss
  63. 26
      static/style/mixin/hr.scss
  64. 16
      static/style/mixin/position-absolute.scss
  65. 5
      static/style/mixin/price-before.scss
  66. 16
      static/style/mixin/text-overflow.scss
  67. 16
      static/style/mixin/triangle.scss
  68. 33
      template.h5.html
  69. 76
      uni.scss
  70. 21
      uview-ui/LICENSE
  71. 105
      uview-ui/README.md
  72. 35
      uview-ui/changelog.md
  73. 74
      uview-ui/components/u--form/u--form.vue
  74. 40
      uview-ui/components/u--image/u--image.vue
  75. 63
      uview-ui/components/u--input/u--input.vue
  76. 46
      uview-ui/components/u--text/u--text.vue
  77. 47
      uview-ui/components/u--textarea/u--textarea.vue
  78. 54
      uview-ui/components/u-action-sheet/props.js
  79. 275
      uview-ui/components/u-action-sheet/u-action-sheet.vue
  80. 59
      uview-ui/components/u-album/props.js
  81. 236
      uview-ui/components/u-album/u-album.vue
  82. 44
      uview-ui/components/u-alert/props.js
  83. 243
      uview-ui/components/u-alert/u-alert.vue
  84. 46
      uview-ui/components/u-avatar-group/props.js
  85. 103
      uview-ui/components/u-avatar-group/u-avatar-group.vue
  86. 78
      uview-ui/components/u-avatar/props.js
  87. 163
      uview-ui/components/u-avatar/u-avatar.vue
  88. 54
      uview-ui/components/u-back-top/props.js
  89. 137
      uview-ui/components/u-back-top/u-back-top.vue
  90. 72
      uview-ui/components/u-badge/props.js
  91. 171
      uview-ui/components/u-badge/u-badge.vue
  92. 46
      uview-ui/components/u-button/nvue.scss
  93. 156
      uview-ui/components/u-button/props.js
  94. 485
      uview-ui/components/u-button/u-button.vue
  95. 73
      uview-ui/components/u-button/vue.scss
  96. 99
      uview-ui/components/u-calendar/header.vue
  97. 570
      uview-ui/components/u-calendar/month.vue
  98. 134
      uview-ui/components/u-calendar/props.js
  99. 288
      uview-ui/components/u-calendar/u-calendar.vue
  100. 85
      uview-ui/components/u-calendar/util.js

25
.gitignore

@ -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*

21
App.vue

@ -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>

128
README.md

@ -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 ```

256
components/nx-search/nx-search.vue

@ -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>

33
main.js

@ -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();

85
manifest.json

@ -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
}
}
}

30
nxTemp/apis/userAPI.js

@ -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);
}

30
nxTemp/config/index.config.js

@ -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];

278
nxTemp/config/requestConfig.js

@ -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;

11
nxTemp/filter/index.js

@ -0,0 +1,11 @@
// 自动添加省略号
export function autoAddPoints(address) {
if (address && address.length > 17) {
return '...'.padStart(17, address);
} else {
return address;
}
}

26
nxTemp/index.js

@ -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
}

129
nxTemp/request/core/request.js

@ -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);
}
}
}
}

84
nxTemp/request/core/utils.js

@ -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
});
}

7
nxTemp/request/index.js

@ -0,0 +1,7 @@
/***************纯粹的数据请求(如果使用这种可以删除掉fileUpload.js)******************/
// import request from "./core/request.js";
// export default request;
/********数据请求同时继承了文件上传(包括七牛云上传)************/
import upload from "./upload/upload.js";
export default upload;

313
nxTemp/request/request.md

@ -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参数奖失去作用)
});
```

169
nxTemp/request/upload/qiniuUploader.js

@ -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;
}
})();

214
nxTemp/request/upload/upload.js

@ -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);
}
}
}
}

234
nxTemp/request/upload/utils.js

@ -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');
}

73
nxTemp/router/index.js

@ -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

19
nxTemp/store/index.js

@ -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;

67
nxTemp/store/modules/user.js

@ -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
}
}
}

165
nxTemp/utils/tools.js

@ -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
}
},
}

35
nxTemp/wechat/wechat.js

@ -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
};

16
package-lock.json

@ -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="
}
}
}

6
package.json

@ -0,0 +1,6 @@
{
"dependencies": {
"uni-read-pages": "^1.0.5",
"uni-simple-router": "^2.0.1"
}
}

152
pages.json

@ -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": [ //pageshttps://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
}
]
}
}

327
pages/event/event_grade.vue

@ -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>

196
pages/event/event_list.vue

@ -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>

118
pages/index/index.vue

@ -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>

27
pages/login/agreement.vue

@ -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>

255
pages/login/forget.vue

@ -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>

242
pages/login/login.vue

@ -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>

303
pages/login/reg.vue

@ -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>

30
pages/me/index.vue

@ -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>

74
pages/message/center_list.vue

@ -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>

69
pages/message/detail.vue

@ -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>

13
pages/public/404.vue

@ -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>

184
static/colorui/animation.css

@ -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)
}
}

69
static/colorui/components/cu-custom.vue

@ -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

BIN
static/images/event/enent_none.png

After

Width: 736  |  Height: 632  |  Size: 27 KiB

BIN
static/images/event/event_close.png

After

Width: 68  |  Height: 68  |  Size: 1.5 KiB

BIN
static/images/event/event_location.png

After

Width: 56  |  Height: 56  |  Size: 4.1 KiB

BIN
static/images/event/event_lock.png

After

Width: 64  |  Height: 64  |  Size: 3.9 KiB

BIN
static/images/index/arrow_right.png

After

Width: 60  |  Height: 60  |  Size: 2.4 KiB

BIN
static/images/index/index_bell.png

After

Width: 104  |  Height: 104  |  Size: 4.8 KiB

BIN
static/images/index/index_bg.png

After

Width: 1500  |  Height: 960  |  Size: 16 KiB

BIN
static/images/index/index_cup.png

After

Width: 104  |  Height: 104  |  Size: 5.0 KiB

BIN
static/images/index/index_logout.png

After

Width: 104  |  Height: 104  |  Size: 5.7 KiB

BIN
static/images/login/agree_circle.png

After

Width: 80  |  Height: 80  |  Size: 3.4 KiB

BIN
static/images/login/agreed_circle.png

After

Width: 88  |  Height: 80  |  Size: 3.8 KiB

BIN
static/images/login/ty0.png

After

Width: 44  |  Height: 44  |  Size: 807 B

BIN
static/images/login/ty1.png

After

Width: 44  |  Height: 44  |  Size: 954 B

BIN
static/images/logo_main.png

After

Width: 348  |  Height: 344  |  Size: 14 KiB

BIN
static/images/tabbar/tab_home_01.png

After

Width: 87  |  Height: 87  |  Size: 1.1 KiB

BIN
static/images/tabbar/tab_home_02.png

After

Width: 87  |  Height: 87  |  Size: 1.0 KiB

BIN
static/images/tabbar/tab_user_01.png

After

Width: 87  |  Height: 87  |  Size: 1.8 KiB

BIN
static/images/tabbar/tab_user_02.png

After

Width: 87  |  Height: 87  |  Size: 2.1 KiB

131
static/style/base.scss

@ -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;
}

164
static/style/mixin/flex.scss

@ -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');
}
}

26
static/style/mixin/hr.scss

@ -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;
}

16
static/style/mixin/position-absolute.scss

@ -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;
}
}

5
static/style/mixin/price-before.scss

@ -0,0 +1,5 @@
// 价格¥前加
%__priceBefore{
font-size: $uni-font-size-sm;
content: "¥";
}

16
static/style/mixin/text-overflow.scss

@ -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;
}
}

16
static/style/mixin/triangle.scss

@ -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;
}
}

33
template.h5.html

@ -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>

76
uni.scss

@ -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;

21
uview-ui/LICENSE

@ -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.

105
uview-ui/README.md

@ -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应用到您的产品中。

35
uview-ui/changelog.md

@ -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参数缺失问题

74
uview-ui/components/u--form/u--form.vue

@ -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>

40
uview-ui/components/u--image/u--image.vue

@ -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>

63
uview-ui/components/u--input/u--input.vue

@ -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>

46
uview-ui/components/u--text/u--text.vue

@ -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>

47
uview-ui/components/u--textarea/u--textarea.vue

@ -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>

54
uview-ui/components/u-action-sheet/props.js

@ -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
}
}
}

275
uview-ui/components/u-action-sheet/u-action-sheet.vue

@ -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",
// propsmethodsmixin
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>

59
uview-ui/components/u-album/props.js

@ -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
}
}
}

236
uview-ui/components/u-album/u-album.vue

@ -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
// weexKPIdom
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
},
// computedurls
//
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"forthis.$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>

44
uview-ui/components/u-alert/props.js

@ -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
}
}
}

243
uview-ui/components/u-alert/u-alert.vue

@ -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>

46
uview-ui/components/u-avatar-group/props.js

@ -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
}
}
}

103
uview-ui/components/u-avatar-group/u-avatar-group.vue

@ -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-childnvue
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>

78
uview-ui/components/u-avatar/props.js

@ -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
}
}
}

163
uview-ui/components/u-avatar/u-avatar.vue

@ -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 {
// randomBgColortrue
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: {
// srcavatarUrlsrc
// 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>

54
uview-ui/components/u-back-top/props.js

@ -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
}
}
}

137
uview-ui/components/u-back-top/u-back-top.vue

@ -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
// rpxpx
if (/rpx$/.test(this.top)) {
top = uni.rpx2px(parseInt(this.top))
} else {
// pxparseInt
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>

72
uview-ui/components/u-badge/props.js

@ -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
}
}
}

171
uview-ui/components/u-badge/u-badge.vue

@ -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) {
// toprightoffsetrighttop
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>

46
uview-ui/components/u-button/nvue.scss

@ -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;
}
}

156
uview-ui/components/u-button/props.js

@ -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
}
}
}

485
uview-ui/components/u-button/u-button.vue

@ -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 按钮的预置样式infoprimaryerrorwarningsuccess (默认 'info' )
* @property {String} size 按钮尺寸largenormalmini 默认 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.bemcomputedmixin
if (!this.color) {
return this.bem(
"button",
["type", "shape", "size"],
["disabled", "plain", "hairline"]
);
} else {
// nvuecolortypetype
return this.bem(
"button",
["shape", "size"],
["disabled", "plain", "hairline"]
);
}
},
loadingColor() {
if (this.plain) {
// colorcolor使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() {
// colorcolor使
// u-iconcolor
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
// weexborderWidth
// 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;
},
// nvuetext
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>

73
uview-ui/components/u-button/vue.scss

@ -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;
}
}
}

99
uview-ui/components/u-calendar/header.vue

@ -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>

570
uview-ui/components/u-calendar/month.vue

@ -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: '结束'
},
// modemultiplerange
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) {
// 0item
week = (week === 0 ? 7 : week) - 1
style.marginLeft = uni.$u.addUnit(week * dayWidth)
}
if (this.mode === 'range') {
// DCloudiOSbug
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 = {}
// dateselected0使dateSameincludes
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) {
// DCloudiOSbug
// nvueiOSuni-appbug
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) {
// 2item
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$nextTick100%
uni.$u.sleep(10).then(() => {
this.getWrapperWidth()
this.getMonthRect()
})
})
},
//
dateSame(date1, date2) {
return dayjs(date1).isSame(dayjs(date2))
},
// nvuecssitem
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++) {
// monthsscroll-view
topArr[i] = height
height += sizes[i].height
}
// this.months[i].top()monthtop使
this.$emit('updateMonthTop', topArr)
})
},
//
getMonthRectByPromise(el) {
// #ifndef APP-NVUE
// $uGetRectuViewhttps://www.uviewui.com/js/getRect.html
// this.$uGetRectthis.$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) {
// 02
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)))
// computedarr
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>

134
uview-ui/components/u-calendar/props.js

@ -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
}
}
}

288
uview-ui/components/u-calendar/u-calendar.vue

@ -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() {
// range1disabled
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: {
// propsref
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
// maxMonthminMonthmaxMonth12
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-60
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) {
// 0scroll-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 = []) {
// toponScroll
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>

85
uview-ui/components/u-calendar/util.js

@ -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
}
})
)
}
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save