Commit 80ff6472 authored by 李苏's avatar 李苏 💬

cxerpapp

parent 3d6e4c57
/unpackage/
/.idea/
<script>
import Vue from 'vue'
import appUpdate from 'common/util/appUpdate.js'
export default {
onLaunch: function() {
uni.getSystemInfo({
success: function(e) {
// #ifdef APP-PLUS
// 检测升级
appUpdate()
// #endif
// #ifndef MP
Vue.prototype.StatusBar = e.statusBarHeight;
if (e.platform == 'android') {
Vue.prototype.CustomBar = e.statusBarHeight + 50;
} else {
Vue.prototype.CustomBar = e.statusBarHeight + 45;
};
// #endif
// #ifdef MP-WEIXIN
Vue.prototype.StatusBar = e.statusBarHeight;
let custom = wx.getMenuButtonBoundingClientRect();
Vue.prototype.Custom = custom;
Vue.prototype.CustomBar = custom.bottom + custom.top - e.statusBarHeight;
// #endif
// #ifdef MP-ALIPAY
Vue.prototype.StatusBar = e.statusBarHeight;
Vue.prototype.CustomBar = e.statusBarHeight + e.titleBarHeight;
// #endif
// #ifdef APP-PLUS
//Vue.prototype.$api.listenTranMsg()
// var info = plus.push.getClientInfo();
// /* 5+ push 消息推送 ps:使用:H5+的方式监听,实现推送*/
// plus.push.addEventListener("click", function(msg) {
// console.log("click:" + JSON.stringify(msg));
// console.log(msg.payload);
// console.log(JSON.stringify(msg));
// //这里可以写跳转业务代码
// }, false);
// // 监听在线消息事件
// plus.push.addEventListener("receive", function(msg) {
// // plus.ui.alert(2);
// //这里可以写跳转业务代码
// console.log("recevice:" + JSON.stringify(msg))
// }, false);
// #endif
//Vue.prototype.$api.initLogin()
}
})
Vue.prototype.NavBarColor='bg-gradual-blue'
Vue.prototype.Radio_Check_Size='scale(0.7)'
Vue.prototype.bannerList=[
{id:1,type: 'image',url: 'https://static.jeecg.com/upload/test/banner0_1595850438042.jpeg', link: ''},
{id:2,type: 'image',url: 'https://static.jeecg.com/upload/test/banner2_1595818081327.jpg', link: ''},
{id:3,type: 'image',url: 'https://static.jeecg.com/upload/test/oabanner-2_1595648520760.png', link: ''},
{id:4,type: 'image',url: 'https://static.jeecg.com/upload/test/banner5_1595818089013.jpeg', link: ''},
]
Vue.prototype.ColorList = [{
title: '嫣红',
name: 'red',
color: '#e54d42'
},
{
title: '桔橙',
name: 'orange',
color: '#f37b1d'
},
{
title: '明黄',
name: 'yellow',
color: '#fbbd08'
},
{
title: '橄榄',
name: 'olive',
color: '#8dc63f'
},
{
title: '森绿',
name: 'green',
color: '#39b54a'
},
{
title: '天青',
name: 'cyan',
color: '#1cbbb4'
},
{
title: '海蓝',
name: 'blue',
color: '#0081ff'
},
{
title: '姹紫',
name: 'purple',
color: '#6739b6'
},
{
title: '木槿',
name: 'mauve',
color: '#9c26b0'
},
{
title: '桃粉',
name: 'pink',
color: '#e03997'
},
{
title: '棕褐',
name: 'brown',
color: '#a5673f'
},
{
title: '玄灰',
name: 'grey',
color: '#8799a3'
},
{
title: '草灰',
name: 'gray',
color: '#aaaaaa'
},
{
title: '墨黑',
name: 'black',
color: '#333333'
},
{
title: '雅白',
name: 'white',
color: '#ffffff'
},
]
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
@import "plugin/colorui/main.css";
@import "plugin/colorui/icon.css";
@import "plugin/colorui/animation.css";
.lccontent {
padding: 60rpx;
padding-left:20%;
.row {
padding: 24rpx 0;
}
}
.bg-blue{
background-color: #0081FF!important;
color: #FFFFFF!important;
}
.newts{
text-align: center;font-size: 16px!important;
}
.newcont{
text-align: center;font-size: 15px!important;
}
.wid100{
width: 100%!important;
}
.tkqx{
width: 50%;background-color: #FFFFFF!important;color: #666;font-size: 16px!important;font-weight: 800;
}
.tkqd{
width: 50%;background-color: #FFFFFF!important;color: #0081FF;font-size: 16px!important;font-weight: 800;
}
.cuIcon-close{
display: none;
}
.nav-list {
display: flex;
flex-wrap: wrap;
padding: 0px 40upx 0px;
justify-content: space-between;
}
.nav-li {
padding: 30upx;
border-radius: 12upx;
width: 45%;
margin: 0 2.5% 40upx;
background-image: url(https://cdn.nlark.com/yuque/0/2019/png/280374/1552996358352-assets/web-upload/cc3b1807-c684-4b83-8f80-80e5b8a6b975.png);
background-size: cover;
background-position: center;
position: relative;
z-index: 1;
}
.nav-li::after {
content: "";
position: absolute;
z-index: -1;
background-color: inherit;
width: 100%;
height: 100%;
left: 0;
bottom: -10%;
border-radius: 10upx;
opacity: 0.2;
transform: scale(0.9, 0.9);
}
.nav-li.cur {
color: #fff;
background: rgb(94, 185, 94);
box-shadow: 4upx 4upx 6upx rgba(94, 185, 94, 0.4);
}
.nav-title {
font-size: 32upx;
font-weight: 300;
}
.nav-title::first-letter {
font-size: 40upx;
margin-right: 4upx;
}
.nav-name {
font-size: 28upx;
text-transform: Capitalize;
margin-top: 20upx;
position: relative;
}
.nav-name::before {
content: "";
position: absolute;
display: block;
width: 40upx;
height: 6upx;
background: #fff;
bottom: 0;
right: 0;
opacity: 0.5;
}
.nav-name::after {
content: "";
position: absolute;
display: block;
width: 100upx;
height: 1px;
background: #fff;
bottom: 0;
right: 40upx;
opacity: 0.3;
}
.nav-name::first-letter {
font-weight: bold;
font-size: 36upx;
margin-right: 1px;
}
.nav-li text {
position: absolute;
right: 30upx;
top: 30upx;
font-size: 52upx;
width: 60upx;
height: 60upx;
text-align: center;
line-height: 60upx;
}
.text-light {
font-weight: 300;
}
@keyframes show {
0% {
transform: translateY(-50px);
}
60% {
transform: translateY(40upx);
}
100% {
transform: translateY(0px);
}
}
@-webkit-keyframes show {
0% {
transform: translateY(-50px);
}
60% {
transform: translateY(40upx);
}
100% {
transform: translateY(0px);
}
}
</style>
This diff is collapsed.
import { http } from '@/common/service/service.js'
import configService from '@/common/service/config.service.js';
console.log("api")
const apiService = {
/**
* 登录
*/
login(params) {
return http.post('/login',params)
},
/**
* 手机号码登录
*/
phoneNoLogin(params) {
return http.post('/sys/phoneLogin',params);
},
/**
* 退出
*/
logout(params) {
return http.post('/sys/logout',params);
},
/**
* 获取文件访问路径
* @param avatar
* @param subStr
* @returns {*}
*/
getFileAccessHttpUrl(avatar,subStr){
if(!subStr) subStr = 'http'
if(avatar && avatar.startsWith(subStr)){
return avatar;
}else{
return configService.staticDomainURL + "/" + avatar;
}
}
};
export default apiService;
import configService from '@/common/service/config.service.js';
import store from '@/store/index.js';
class socket {
constructor(options) {
this.socketUrl = configService.apiUrl;
this.socketStart = false;
this.monitorSocketError();
this.monitorSocketClose();
this.socketReceive();
}
init(socket_type,callback) {
const _this = this;
if (configService.apiUrl) {
if(this.socketStart){
console.log('webSocket已经启动了');
}else{
let userid=store.state.userid?store.state.userid:store.getters.userid;
let url=this.socketUrl.replace("https://","wss://").replace("http://","ws://")+"/"+socket_type+"/"+userid+"_app";
console.log("启动this.socketUrl连接地址:",url);
uni.connectSocket({
url: url,
method: 'GET'
});
uni.onSocketOpen((res) => {
this.socketStart = true;
callback && callback();
console.log('WebSocket连接已打开!');
});
/*setTimeout(() => {
_this.getHeartbeat();
}, 5000);*/
}
}else{
console.log('config/baseUrl socketUrl为空');
}
}
//Socket给服务器发送消息
send(data, callback) {
const _this = this;
if (store.state.userid) {
data.userUid =store.state.userid;
}
console.log(data);
uni.sendSocketMessage({
data: JSON.stringify(data),
success: () => {
callback && callback(true);
},
fail: () => {
callback && callback(false);
}
});
}
//Socket接收服务器发送过来的消息
socketReceive() {
const _this = this;
uni.onSocketMessage(function(res) {
console.log("APP:----》收到服务器内容:",res);
let data = JSON.parse(res.data);
//console.log('收到服务器内容:', data);
_this.acceptMessage && _this.acceptMessage(data);
});
}
//关闭Socket
closeSocket() {
const _this = this;
uni.closeSocket();
_this.socketStart = false;
}
//监听Socket关闭
monitorSocketClose() {
const _this = this;
uni.onSocketClose(function(res) {
console.log('WebSocket 已关闭!');
_this.socketStart = false;
setTimeout(function() {
//_this.init();
}, 3000);
});
}
//监听Socket错误
monitorSocketError() {
const _this = this;
uni.onSocketError(function(res) {
_this.socketStart = false;
console.log('WebSocket连接打开失败,请检查!');
});
}
//心跳
getHeartbeat() {
const _this = this;
this.send({
type: "心跳",
userUid: store.state.userid
}, (val) => {
setTimeout(() => {
if (val) {
//_this.getHeartbeat();
} else {
if(!_this.socketStart){
//_this.init();
}
}
}, 10000);
});
}
};
const mySocket = new socket();
export default mySocket;
This diff is collapsed.
'use strict'
import isAbsoluteURL from '../helpers/isAbsoluteURL'
import combineURLs from '../helpers/combineURLs'
/**
* Creates a new URL by combining the baseURL with the requestedURL,
* only when the requestedURL is not already an absolute URL.
* If the requestURL is absolute, this function returns the requestedURL untouched.
*
* @param {string} baseURL The base URL
* @param {string} requestedURL Absolute or relative URL to combine
* @returns {string} The combined full path
*/
export default function buildFullPath(baseURL, requestedURL) {
if (baseURL && !isAbsoluteURL(requestedURL)) {
return combineURLs(baseURL, requestedURL)
}
return requestedURL
}
'use strict'
import * as utils from './../utils'
function encode(val) {
return encodeURIComponent(val).
replace(/%40/gi, '@').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%20/g, '+').
replace(/%5B/gi, '[').
replace(/%5D/gi, ']')
}
/**
* Build a URL by appending params to the end
*
* @param {string} url The base of the url (e.g., http://www.google.com)
* @param {object} [params] The params to be appended
* @returns {string} The formatted url
*/
export default function buildURL(url, params) {
/*eslint no-param-reassign:0*/
if (!params) {
return url
}
var serializedParams
if (utils.isURLSearchParams(params)) {
serializedParams = params.toString()
} else {
var parts = []
utils.forEach(params, function serialize(val, key) {
if (val === null || typeof val === 'undefined') {
return
}
if (utils.isArray(val)) {
key = key + '[]'
} else {
val = [val]
}
utils.forEach(val, function parseValue(v) {
if (utils.isDate(v)) {
v = v.toISOString()
} else if (utils.isObject(v)) {
v = JSON.stringify(v)
}
parts.push(encode(key) + '=' + encode(v))
})
})
serializedParams = parts.join('&')
}
if (serializedParams) {
var hashmarkIndex = url.indexOf('#')
if (hashmarkIndex !== -1) {
url = url.slice(0, hashmarkIndex)
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
}
return url
}
'use strict'
/**
* Creates a new URL by combining the specified URLs
*
* @param {string} baseURL The base URL
* @param {string} relativeURL The relative URL
* @returns {string} The combined URL
*/
export default function combineURLs(baseURL, relativeURL) {
return relativeURL
? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
: baseURL
}
'use strict'
/**
* Determines whether the specified URL is absolute
*
* @param {string} url The URL to test
* @returns {boolean} True if the specified URL is absolute, otherwise false
*/
export default function isAbsoluteURL(url) {
// A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
// RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
// by any combination of letters, digits, plus, period, or hyphen.
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url)
}
import Request from './core/Request'
export default Request
'use strict'
// utils is a library of generic helper functions non-specific to axios
var toString = Object.prototype.toString
/**
* Determine if a value is an Array
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an Array, otherwise false
*/
export function isArray (val) {
return toString.call(val) === '[object Array]'
}
/**
* Determine if a value is an Object
*
* @param {Object} val The value to test
* @returns {boolean} True if value is an Object, otherwise false
*/
export function isObject (val) {
return val !== null && typeof val === 'object'
}
/**
* Determine if a value is a Date
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Date, otherwise false
*/
export function isDate (val) {
return toString.call(val) === '[object Date]'
}
/**
* Determine if a value is a URLSearchParams object
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a URLSearchParams object, otherwise false
*/
export function isURLSearchParams (val) {
return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams
}
/**
* Iterate over an Array or an Object invoking a function for each item.
*
* If `obj` is an Array callback will be called passing
* the value, index, and complete array for each item.
*
* If 'obj' is an Object callback will be called passing
* the value, key, and complete object for each property.
*
* @param {Object|Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
*/
export function forEach (obj, fn) {
// Don't bother if no value provided
if (obj === null || typeof obj === 'undefined') {
return
}
// Force an array if not already something iterable
if (typeof obj !== 'object') {
/*eslint no-param-reassign:0*/
obj = [obj]
}
if (isArray(obj)) {
// Iterate over array values
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj)
}
} else {
// Iterate over object keys
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj)
}
}
}
}
/**
* 是否为boolean 值
* @param val
* @returns {boolean}
*/
export function isBoolean(val) {
return typeof val === 'boolean'
}
import modules from './modules'
import Vue from 'vue'
import Router from '@/plugin/uni-simple-router/index.js'
import {ACCESS_TOKEN} from '@/common/util/constants.js'
console.log("Router")
Vue.use(Router)
//初始化
const router = new Router({
encodeURI:true,
routes: [...modules]//路由表
});
console.log(router)
const whiteList = ['/pages/login/login']
//全局路由前置守卫
router.beforeEach((to, from, next) => {
console.log(from)
let token=uni.getStorageSync("ACCESS_TOKEN");
if(token){
next()
}else if(to.name=="fwqsz"){
next()
}else{
if (whiteList.indexOf(to.path) !== -1) {
next()
}else{
next({ path: '/pages/login/login'})
}
}
next()
})
// 全局路由后置守卫
router.afterEach((to, from) => {
console.log("afterEach")
})
export default router;
\ No newline at end of file
const files = require.context('.', false, /\.js$/)
const modules = []
files.keys().forEach(key => {
if (key === './index.js') return
const item = files(key).default
modules.push(...item)
})
export default modules
\ No newline at end of file
const routes = [
{
path: "/pages/login/login",
name: 'login',
meta: {
title: '登录',
},
},
{
path: "/pages/login/fwqsz",
name: 'fwqsz',
meta: {
title: '服务器设置',
},
},
{
//注意:path必须跟pages.json中的地址对应,最前面别忘了加'/'哦
path: '/pages/index/index',
name: 'index',
meta: {
title: '主页',
},
},
{
//注意:path必须跟pages.json中的地址对应,最前面别忘了加'/'哦
path: '/pages/home/home',
//aliasPath:'/', //对于h5端你必须在首页加上aliasPath并设置为/
name: 'home',
meta: {
title: '首页',
},
},
{
path: '/pages/user/people',
name: 'people',
meta: {
title: '个人中心',
},
},
{
path: '/pages/user/setting',
name: 'setting',
meta: {
title: '用户设置',
},
},
{
path: '/pages/user/userdetail',
name: 'userdetail',
meta: {
title: '个人详情',
},
},
{
path: '/pages/user/useredit',
name: 'useredit',
meta: {
title: '个人编辑',
},
},
{
path: '/pages/user/userexit',
name: 'userexit',
meta: {
title: '退出',
},
},
// {
// path: '/pages/user/location',
// name: 'location',
// meta: {
// title: '定位',
// },
// },
{
path: '/pages/common/exit',
name: 'exit',
meta: {
title: '退出',
},
},
{
path: '/pages/common/success',
name: 'success',
meta: {
title: 'success',
},
},{
path: '/pages/addressbook/address-book',
name: 'addressBook',
meta: {
title: 'addressBook',
},
},
{
path: '/pages/addressbook/level-address-book',
name: 'levelAddressBook',
meta: {
title: 'levelAddressBook',
},
},
{
path: '/pages/addressbook/member',
name: 'member',
meta: {
title: 'member',
},
},
{
path: '/pages/addressbook/address-detail',
name: 'addressDetail',
meta: {
title: 'addressDetail',
},
},
{
path: '/pages/annotation/annotationList',
name: 'annotationList',
meta: {
title: '通知公告',
},
},
{
path: '/pages/annotation/annotationDetail',
name: 'annotationDetail',
meta: {
title: '通知详情',
},
},
// {
// path: '/pages/common/helloWorld',
// name: 'helloWorld',
// meta: {
// title: 'helloWorld',
// },
// },
// {
// path: '/pages/common/ygqj',
// name: 'ygqj',
// meta: {
// title: 'ygqj',
// },
// },
// {
// path: '/pages/common/qjxq',
// name: 'qjxq',
// meta: {
// title: '请假详情',
// },
// },
// {
// path: '/pages/common/fybx',
// name: 'fybx',
// meta: {
// title: '费用报销',
// },
// },
// {
// path: '/pages/common/fybxxq',
// name: 'fybxxq',
// meta: {
// title: '费用报销详情',
// },
// },
// {
// path: '/pages/process/process',
// name: 'process',
// meta: {
// title: 'process',
// },
// },
// {
// path: '/pages/process/dbswgzrw',
// name: 'dbswgzrw',
// meta: {
// title:'dbswgzrw',
// },
// },
// {
// path: '/pages/process/dbswfybx',
// name: 'dbswfybx',
// meta: {
// title:'dbswfybx',
// },
// },
// {
// path: '/pages/process/toprocessing',
// name: 'toprocessing',
// meta: {
// title: '处理',
// },
// },
// {
// path: '/pages/process/tockxx',
// name: 'tockxx',
// meta: {
// title: '查看详情',
// },
// },
// {
// path: '/pages/process/wdswfybx',
// name: 'wdswfybx',
// meta: {
// title: 'wdswfybx',
// },
// },
// {
// path: '/pages/process/qjdedit',
// name: 'qjdedit',
// meta: {
// title: '编辑请假单',
// },
// },
{
path: '/pages/process/fybxdedit',
name: 'fybxdedit',
meta: {
title: '编辑费用报销单',
},
},
{
path: '/pages/gzrw/gzrwcj',
name: 'gzrwcj',
meta: {
title: '工作任务创建',
},
},
{
path: '/pages/gzrw/gzrwedit',
name: 'gzrwedit',
meta: {
title: '工作任务编辑',
},
},
{
path: '/pages/gzrw/gzrwxq',
name: 'gzrwxq',
meta: {
title: '工作任务详情',
},
},
{
path: '/pages/addLovedata/addLovedata',
name: 'addLovedata',
meta: {
title: '添加我的应用',
},
},
// {
// path: '/pages/cgjh/cgjh',
// name: 'cgjh',
// meta: {
// title: '采购计划审批',
// },
// },
// {
// path: '/pages/cgjh/cgjhxq',
// name: 'cgjhxq',
// meta: {
// title: '采购计划审批详情',
// },
// },
{
path: '/pages/xsddsp/xsddsp',
name: 'xsddsp',
meta: {
title: '销售订单审批',
},
},
{
path: '/pages/cgddsp/cgddsp',
name: 'cgddsp',
meta: {
title: '采购订单审批',
},
},
{
path: '/pages/xsddsp/allcklc',
name: 'allcklc',
meta: {
title: '查看流程',
},
},
//明细查询
{
path: '/pages/mxcxxsdd/index',
name: 'mxcxxsdd',
meta: {
title: '销售订单明细查询',
},
},
{
path: '/pages/mxcxxsdd/ckxq',
name: 'ckxq',
meta: {
title: '销售订单明细详情',
},
},
{
path: '/pages/mxcxcgdd/index',
name: 'mxcxcgdd',
meta: {
title: '采购订单明细查询',
},
},
{
path: '/pages/mxcxcgdd/ckxq',
name: 'cgddckxq',
meta: {
title: '采购订单明细查询',
},
},
{
path: '/pages/mxcxsfhz/index',
name: 'mxcxsfhz',
meta: {
title: '收发汇总查询',
},
},
{
path: '/pages/mxcxsfhz/ckxq',
name: 'sfhzckxq',
meta: {
title: '收发汇总明细查询',
},
},
]
export default routes
\ No newline at end of file
let BASE_URL = ''
let fwq=""
// 修改
try {
fwq = uni.getStorageSync('fwq');
} catch (e) {
// error
}
let newUrl = 'http://'+fwq
console.log(BASE_URL)
if (process.env.NODE_ENV == 'development') {
BASE_URL = newUrl
// 开发环境
} else {
BASE_URL = newUrl
// 生产环境
}
let staticDomainURL = BASE_URL+ '/sys/common/static';
const configService = {
apiUrl: BASE_URL,
staticDomainURL: staticDomainURL
};
export default configService
import Request from '@/common/luch-request/index.js'
import {ACCESS_TOKEN} from '@/common/util/constants.js'
import configService from './config.service.js'
import tip from '@/common/util/tip.js';
import store from '@/store/index.js';
import Vue from 'vue'
let apiUrl = configService.apiUrl;
const getTokenStorage = () => {
let token = ''
try{
token = uni.getStorageSync("ACCESS_TOKEN")
}catch(e){
//TODO handle the exception
console.log("getTokenStorage",token)
}
return token
}
const http = new Request()
http.setConfig((config) => { /* 设置全局配置 */
config.baseUrl = apiUrl /* 根域名不同 */
config.header = {
...config.header
}
return config
})
/**
* 自定义验证器,如果返回true 则进入响应拦截器的响应成功函数(resolve),否则进入响应拦截器的响应错误函数(reject)
* @param { Number } statusCode - 请求响应体statusCode(只读)
* @return { Boolean } 如果为true,则 resolve, 否则 reject
*/
// 有默认,非必写
http.validateStatus = (statusCode) => {
return statusCode === 200
}
http.interceptor.request((config, cancel) => { /* 请求之前拦截器 */
config.header = {
...config.header,
'X-Access-Token':getTokenStorage()
}
/*
if (!token) { // 如果token不存在,调用cancel 会取消本次请求,但是该函数的catch() 仍会执行
cancel('token 不存在') // 接收一个参数,会传给catch((err) => {}) err.errMsg === 'token 不存在'
}
*/
return config
})
// 必须使用异步函数,注意
http.interceptor.response(async (response) => { /* 请求之后拦截器 */
// if (response.data.code !== 200) { // 服务端返回的状态码不等于200,则reject()
// return Promise.reject(response)
// }
return response
}, (response) => {
console.log("请求错误做点什么",response);
if (response) {
let data = response.data
const token = uni.getStorageSync(ACCESS_TOKEN)
console.log("------异常响应------",token)
console.log("------异常响应------",data.status)
switch (data.status) {
case 403:
tip.error('拒绝访问');
break
case 500:
if(!token || data.message=="Token失效,请重新登录"){
let timeout=setTimeout(tip.alert('登录已过期'), 1000);
store.dispatch('Logout').then(() => {
clearTimeout(timeout)
window.location.reload()
})
}
break
case 404:
break
case 504:
break
case 401:
if (token) {
/* store.dispatch('Logout').then(() => {
setTimeout(() => {
window.location.reload()
}, 1500)
}) */
}
break
default:
tip.error({
duration: 0,
forbidClick: true,
message: data.message
});
break
}
}
return response
})
export {
http
}
export default http
let cacheMap = new Map()
let timeoutDefault = 1200
function isTimeout (name) {
const data = cacheMap.get(name)
if (!data) return true
if (data.timeout === 0) return false
const currentTime = Date.now()
const overTime = (currentTime - data.createTime) / 1000
if (overTime > data.timeout) {
cacheMap.delete(name)
if (name.startsWith('_')) {
try {
uni.removeStorageSync(name)
} catch (e) {
console.log(e)
}
}
return true
}
return false
}
class CacheCell {
constructor (data, timeout) {
this.data = data
this.timeout = timeout
this.createTime = Date.now()
}
}
class MinCache {
constructor (timeout) {
try {
const res = uni.getStorageInfoSync()
res.keys.forEach(name => {
try {
const value = uni.getStorageSync(name)
cacheMap.set(name, value)
} catch (e) {
console.log(e)
}
})
} catch (e) {
console.log(e)
}
timeoutDefault = timeout
}
set (name, data, timeout = timeoutDefault) {
const cachecell = new CacheCell(data, timeout)
let cache = null
if (name.startsWith('_')) {
try {
uni.setStorageSync(name, cachecell)
cache = cacheMap.set(name, cachecell)
} catch (e) {
console.log(e)
}
} else {
cache = cacheMap.set(name, cachecell)
}
return cache
}
get (name) {
return isTimeout(name) ? null : cacheMap.get(name).data
}
delete (name) {
let value = false
if (name.startsWith('_')) {
try {
uni.removeStorageSync(name)
value = cacheMap.delete(name)
} catch (e) {
console.log(e)
}
} else {
value = cacheMap.delete(name)
}
return value
}
has (name) {
return !isTimeout(name)
}
clear () {
let value = false
try {
uni.clearStorageSync()
cacheMap.clear()
value = true
} catch (e) {
console.log(e)
}
return value
}
}
MinCache.install = function (Vue, {timeout = 1200} = {}) {
Vue.prototype.$cache = new MinCache(timeout)
}
export default MinCache
//APP更新
export default function appUpdate() {
uni.request({
url: 'http://app.jeecg.com/update.json', //检查更新的服务器地址
data: {
appid: plus.runtime.appid,
version: plus.runtime.version,
imei: plus.device.imei
},
success: (res) => {
plus.runtime.getProperty(plus.runtime.appid, function(wgtinfo) {
let client_version = wgtinfo.version
var flag_update = client_version.split(".").splice(0, 2).join(".") != res.data.version.split(".").splice(0, 2)
.join(".")
var flag_hot = (Number(client_version.split(".")[2]) < Number(res.data.version.split(".")[2])) & !flag_update
console.log(client_version)
console.log(flag_update)
console.log(flag_hot)
if (flag_update) {
// 提醒用户更新
uni.showModal({
title: '更新提示',
content: res.data.note,
success: (showResult) => {
if (showResult.confirm) {
plus.nativeUI.toast("正在准备环境,请稍后!");
console.log(res.data.url, )
var dtask = plus.downloader.createDownload(res.data.url, {
method: 'GET',
filename: '_doc/update/'
}, function(d, status) {
if (status == 200) {
var path = d.filename; //下载apk
plus.runtime.install(path); // 自动安装apk文件
} else {
plus.nativeUI.alert('版本更新失败:' + status);
}
});
dtask.start();
}
}
})
} else if (flag_hot) {
uni.downloadFile({
url: res.data.wgtUrl,
success: (downloadResult) => {
console.log(downloadResult.tempFilePath)
if (downloadResult.statusCode === 200) {
plus.nativeUI.toast(`正在热更新!${res.data.versionCode}`);
plus.runtime.install(downloadResult.tempFilePath, {
force: false
}, function() {
plus.nativeUI.toast("热更新成功");
plus.runtime.restart();
}, function(e) {
console.log(e)
plus.nativeUI.toast(`热更新失败:${e.message}`);
});
}
}
});
}
});
}
})
}
export const ACCESS_TOKEN = 'Access-Token'
export const USER_NAME = 'login_username'
export const USER_INFO = 'login_user_info'
const STORAGE_OPTIONS = {
namespace: 'pro__', // key prefix
name: 'ls', // name variable Vue.[ls] or this.[$ls],
storage: 'local', // storage name session, local, memory
}
export default STORAGE_OPTIONS;
// 对Date的扩展,将 Date 转化为指定格式的String
// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
Date.prototype.Format = function (fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt))
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt))
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
export function formatTimeToStr(times, pattern) {
var d = new Date(times).Format("yyyy-MM-dd hh:mm:ss");
if (pattern) {
d = new Date(times).Format(pattern);
}
return d.toLocaleString();
}
\ No newline at end of file
This diff is collapsed.
/**
* 提示与加载工具类
*/
export default class Tips {
constructor() {
this.isLoading = false;
}
/**
* 弹出提示框
*/
static success(title, duration = 1000) {
setTimeout(() => {
uni.showToast({
title: title,
icon: "success",
mask: true,
duration: duration
});
}, 300);
if (duration > 0) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, duration);
});
}
}
/**
* 弹出确认窗口
*/
static confirm(text,showCancel, payload = {}, title = "提示") {
return new Promise((resolve, reject) => {
uni.showModal({
title: title,
content: text,
showCancel: showCancel,
success: res => {
if (res.confirm) {
resolve(payload);
} else if (res.cancel) {
reject(payload);
}
},
fail: res => {
reject(payload);
}
});
});
}
static toast(title, onHide, icon = "none") {
setTimeout(() => {
uni.showToast({
title: title,
icon: icon,
mask: true,
duration:1000
});
}, 300);
// 隐藏结束回调
if (onHide) {
setTimeout(() => {
onHide();
}, 500);
}
}
/**
* 警告框
*/
static alert(title) {
uni.showToast({
title: title,
image: "../../static/alert.png",
mask: true,
duration: 1500
});
}
/**
* 错误框
*/
static error(title, onHide) {
uni.showToast({
title: title,
image: "../../static/error.png",
mask: true,
duration: 1500
});
// 隐藏结束回调
if (onHide) {
setTimeout(() => {
onHide();
}, 500);
}
}
/**
* 弹出加载提示
*/
static loading(title = "加载中") {
if (Tips.isLoading) {
return;
}
Tips.isLoading = true;
uni.showLoading({
title: title,
mask: true
});
}
/**
* 加载完毕
*/
static loaded() {
if (Tips.isLoading) {
Tips.isLoading = false;
uni.hideLoading();
}
}
}
/**
* 静态变量,是否加载中
*/
Tips.isLoading = false;
import { pinyin } from './constants.js';
export default {
chineseToPinYin: function (l1) {
var l2 = l1.length;
var I1 = '';
var reg = new RegExp('[a-zA-Z0-9]');
for (var i = 0; i < l2; i++) {
var val = l1.substr(i, 1);
var name = this.arraySearch(val, pinyin);
if (reg.test(val)) {
I1 += val;
} else if (name !== false) {
I1 += name;
}
}
I1 = I1.replace(/ /g, '-');
while (I1.indexOf('--') > 0) {
I1 = I1.replace('--', '-');
}
return I1;
},
arraySearch: function (l1, l2) {
for (var name in pinyin) {
if (pinyin[name].indexOf(l1) !== -1) {
return this.ucfirst(name);
}
}
return false;
},
ucfirst: function (l1) {
if (l1.length > 0) {
var first = l1.substr(0, 1).toUpperCase();
var spare = l1.substr(1, l1.length);
return first + spare;
}
}
};
/**
* 常用服务
* useful server
*/
const icon_prefix="/static/home/128/"
export const us = {
data:[
// {
// title:"日报",
// icon:icon_prefix+"richang.png",
// description:"记录每天的工作经验和心得",
// useCount:1000,
// page:'process'
// },{
// title:"周报",
// icon:icon_prefix+"zhoubao.png",
// description:"总结每周的工作情况和下周计划",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"考勤",
// icon:icon_prefix+"kaoqin.png",
// description:"工作考勤",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"日程",
// icon:icon_prefix+"richeng.png",
// description:"建立和查看个人工作安排",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"请假申请",
// icon:icon_prefix+"qingjia1.png",
// description:"请假申请",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"出差申请",
// icon:icon_prefix+"chuchai.png",
// description:"出差申请",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"公文发文",
// icon:icon_prefix+"gongwen.png",
// description:"公文发文",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"通知公告",
// icon:icon_prefix+"tongzhi.png",
// description:"查看企业对员工下发的通知公告",
// useCount:10000,
// page:'annotationList'
// },{
// title:"内部邮件",
// icon:icon_prefix+"youjian.png",
// description:"查看内部消息",
// useCount:10000,
// dot:false,
// page:'helloWorld'
// },{
// title:"通讯录",
// icon:icon_prefix+"tongxun.png",
// description:"查看部门,组员",
// useCount:10000,
// page:'levelAddressBook'
// }
],
loveData:[
{
title:"请假",
icon:icon_prefix+"qingjia2.png",
description:"请假",
useCount:1000,
page:'ygqj'
},
{
title:"报销",
icon:icon_prefix+"bx.png",
description:"报销",
useCount:10000,
page:'fybx'
},
{
title:"工作任务",
icon:icon_prefix+"jiaban.png",
description:"工作任务",
useCount:10000,
page:'gzrwcj'
},
// {
// title:"日报",
// icon:icon_prefix+"richang.png",
// description:"记录每天的工作经验和心得",
// useCount:1000,
// page:'process'
// },{
// title:"周报",
// icon:icon_prefix+"zhoubao.png",
// description:"总结每周的工作情况和下周计划",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"考勤",
// icon:icon_prefix+"kaoqin.png",
// description:"工作考勤",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"日程",
// icon:icon_prefix+"richeng.png",
// description:"建立和查看个人工作安排",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"全部",
// icon:icon_prefix+"qingjia1.png",
// description:"全部"
// }
],
ywdata:[
// {
// title:"日报",
// icon:icon_prefix+"richang.png",
// description:"记录每天的工作经验和心得",
// useCount:1000,
// page:'helloWorld'
// },{
// title:"周报",
// icon:icon_prefix+"zhoubao.png",
// description:"总结每周的工作情况和下周计划",
// useCount:10000,
// page:'helloWorld'
// },
// {
// title:"合同",
// icon:icon_prefix+"hetong.png",
// description:"合同",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"会议",
// icon:icon_prefix+"huiyi.png",
// description:"会议",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"客户关系",
// icon:icon_prefix+"tongzhi.png",
// description:"客户关系",
// useCount:10000,
// page:'helloWorld'
// }
],
cwdata:[
{
title:"报销",
icon:icon_prefix+"bx.png",
description:"报销",
useCount:10000,
page:'fybx'
},
// {
// title:"采购",
// icon:icon_prefix+"cg.png",
// description:"采购",
// useCount:10000,
// page:'helloWorld'
// },{
// title:"备用金申请",
// icon:icon_prefix+"byjsq.png",
// description:"备用金申请",
// useCount:10000,
// page:'gzrwcj'
// }
],
system:[
{
title:"采购计划审批",
icon:icon_prefix+"ztnew.png",
description:"采购计划审批",
useCount:1000,
page:'cgjh'
},
{
title:"领料单审批",
icon:icon_prefix+"renwu.png",
description:"领料单审批",
useCount:1000,
page:'lldsp'
},
{
title:"采购订单审批",
icon:icon_prefix+"hetong.png",
description:"采购订单审批",
useCount:1000,
page:'cgddsp'
},
{
title:"销售订单审批",
icon:icon_prefix+"liucheng.png",
description:"销售订单审批",
useCount:1000,
page:'xsddsp'
},
// {
// title:"出差",
// icon:icon_prefix+"chuchai2.png",
// description:"出差",
// useCount:1000,
// page:'helloWorld'
// },
// {
// title:"加班",
// icon:icon_prefix+"jiaban.png",
// description:"加班",
// useCount:1000,
// page:'helloWorld'
// },
],
mxcx:[
{
title:"销售订单明细查询",
icon:icon_prefix+"ztnew.png",
description:"销售订单明细查询",
useCount:1000,
page:'mxcxxsdd'
},
{
title:"采购订单明细查询",
icon:icon_prefix+"renwu.png",
description:"采购订单明细查询",
useCount:1000,
page:'mxcxcgdd'
},
{
title:"收发汇总查询",
icon:icon_prefix+"wendang.png",
description:"收发汇总明细查询",
useCount:1000,
page:'mxcxsfhz'
}
]
}
/**
* other server 其他服务
*/
export const os = {
data:[
{
title:"新闻中心",
icon:icon_prefix+"xinwen.png",
description:"新闻中心",
useCount:10000,
page:'helloWorld'
},{
title:"投票中心",
icon:icon_prefix+"toupiao.png",
description:"投票中心",
useCount:10000,
page:'helloWorld'
},{
title:"任务中心",
icon:icon_prefix+"renwu.png",
description:"任务中心",
useCount:10000,
page:'helloWorld'
},{
title:"文档中心",
icon:icon_prefix+"wendang.png",
description:"文档中心",
useCount:10000,
page:'helloWorld'
}
]
}
\ No newline at end of file
<template>
<view class="sidebar">
<view class="sidebar_tab">
<view class="sidebar_ico">
</view>
<view class="sidebar_title">
{{title}}
</view>
</view>
<slot></slot>
</view>
</template>
<script>
export default {
name: 'm-sidebar',
props:{
title: String,
},
data() {
return{}
}
}
</script>
<style lang="scss" scoped>
.sidebar_tab{
display: flex;
align-items: center;
.sidebar_ico{
border-width: 0px;
width: 2px;
height: 18px;
background: inherit;
background-color: rgba(19, 98, 253, 1);
border: none;
border-radius: 2px;
box-shadow: none;
}
.sidebar_title{
margin-left: 20rpx;
font-weight: bold;
font-size: 32rpx;
}
}
</style>
<template>
<view class="steps">
<view class="date">
<view class="">
<text>{{item[date] | formatTime('yyyy-MM-dd')}}</text>
<slot name='dateTop'></slot>
</view>
<!-- <view class="">
<text>{{item[date] | formatTime('hh:mm:ss')}}</text>
<slot name='dateBot'></slot>
</view> -->
</view>
<view class="tail" :class="{'active-tail':activity === index}"></view>
<view class="node" :class="{'active-node':activity === index}"></view>
<view class="wrapper">
<view class="">
<text>{{item[wrapperStatus]}}</text>
<slot name='status'></slot>
</view>
<view class="wrapperTitle">
<text>{{wrapperTitle}}</text>
<slot name='content'></slot>
</view>
</view>
</view>
</template>
<script>
/*
activity: 当前进度条状态
wrapperStatus:流程状态对应字段
wrapperTitle:详情对应字段
index:进度条
date:时间
*/
export default {
name: 'm-steps',
data() {
return {
}
},
filters: {
filterzt:function(a){
const ztList = {"P": "计划", "F":"确认","C":"完成","I":"开始","S":"审批"};
return ztList[a]
},
jqfilters:function(a){
const ztList = {"BJ": "病假", "SJ":"事假","JB":"加班","TS":"调休","QT":"其他"};
return ztList[a]
},
ztTrans: function(a) {
const ztList = {0: "创建", 1:"提交",2:"退回",3:"撤回",4:"转交",5:"调度",8:"待处理"};
return ztList[a]
},
formatTime: function(time) {
if(time!=null&&time!="")
{
var date = new Date(time);
return date.Format("MM-dd hh:mm")
}else{
return "";
}
},
formatDate:function(time){
if(time!=null&&time!="")
{
var date = new Date(time);
return date.Format("yyyy-MM-dd")
}else{
return "";
}
}
},
props: {
item: {
type: Object,
},
activity: {
type: Number,
default: 0
},
wrapperStatus: {
type: String,
default: 'status'
},
wrapperTitle: {
type: String,
default: 'content'
},
date: {
type: String,
default: 'date'
},
index: {
type: Number,
default: 0
}
}
}
</script>
<style lang="scss" scoped>
.steps {
position: relative;
padding-bottom: 40rpx;
&:last-child {
.tail {
display: none;
}
}
.date {
position: absolute;
top: -6rpx;
color: #AAAAAA;
text-align: center;
font-size: 30rpx;
}
.active-tail {
border-left: 2px solid rgb(11, 189, 135) !important
}
.active-node {
background-color: rgb(11, 189, 135) !important;
}
.tail {
position: absolute;
left: 200rpx;
height: 100%;
border-left: 4rpx solid rgb(11, 189, 135)
}
.node {
left: 190rpx;
width: 24rpx;
height: 24rpx;
position: absolute;
background-color: rgb(11, 189, 135);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.wrapper {
width: 100%!important;
position: relative;
padding-left: 240rpx;
top: -6rpx;
.wrapperTitle{
color: #AAAAAA;
}
}
}
</style>
<template>
<view v-if="tabs && tabs.length" class="app-tabs" :class="{'tabs-fixed': fixed}">
<view class="tabs-item">
<view class="tab-item" v-for="(tab, i) in tabs" :class="{'active': value===i}" :key="i" @click="tabClick(i)">
{{getTabName(tab)}}
</view>
</view>
<view class="tabs-line" :style="{left:lineLift}"></view>
</view>
</template>
<script>
export default {
props:{
tabs: Array,
value: { // 当前显示的下标 (使用v-model语法糖: 1.props需为value; 2.需回调input事件)
type: [String, Number],
default(){
return 0
}
},
fixed: Boolean // 是否悬浮,默认false
},
computed: {
lineLift() {
return 100/this.tabs.length*(this.value + 1) - 100/(this.tabs.length*2) + '%'
}
},
methods: {
getTabName(tab){
return typeof tab === "object" ? tab.name : tab
},
tabClick(i){
if(this.value!=i){
this.$emit("input",i);
this.$emit("change",i);
}
}
}
}
</script>
<style>
.app-tabs{
position: relative;
height: 80rpx;
line-height: 80rpx;
font-size: 24rpx;
background-color: #fff;
border-bottom: 1rpx solid #eee;
}
.app-tabs .tabs-item{
display: flex;
text-align: center;
font-size: 28rpx;
}
.app-tabs .tabs-item .tab-item{
flex: 1;
}
.app-tabs .tabs-item .active{
color: red;
}
.app-tabs .tabs-line{
position: absolute;
bottom: 0;
width: 150rpx;
height: 4rpx;
transform: translateX(-50%);
border-radius: 4rpx;
transition: left .3s;
background: red;
}
/*悬浮*/
.app-tabs.tabs-fixed{
z-index: 9999;
position: fixed;
top: var(--window-top);
left: 0;
width: 100%;
}
</style>
/* 下拉刷新区域 */
.mescroll-downwarp {
position: absolute;
top: -100%;
left: 0;
width: 100%;
height: 100%;
text-align: center;
}
/* 下拉刷新--内容区,定位于区域底部 */
.mescroll-downwarp .downwarp-content {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
min-height: 60rpx;
padding: 20rpx 0;
text-align: center;
}
/* 下拉刷新--提示文本 */
.mescroll-downwarp .downwarp-tip {
display: inline-block;
font-size: 28rpx;
vertical-align: middle;
margin-left: 16rpx;
/* color: gray; 已在style设置color,此处删去*/
}
/* 下拉刷新--旋转进度条 */
.mescroll-downwarp .downwarp-progress {
display: inline-block;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid gray;
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
vertical-align: middle;
}
/* 旋转动画 */
.mescroll-downwarp .mescroll-rotate {
animation: mescrollDownRotate 0.6s linear infinite;
}
@keyframes mescrollDownRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
\ No newline at end of file
<!-- 下拉刷新区域 -->
<template>
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content">
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform':downRotate}"></view>
<view class="downwarp-tip">{{downText}}</view>
</view>
</view>
</template>
<script>
export default {
props: {
option: Object , // down的配置项
type: Number, // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1)
},
computed: {
// 支付宝小程序需写成计算属性,prop定义default仍报错
mOption(){
return this.option || {}
},
// 是否在加载中
isDownLoading(){
return this.type === 3
},
// 旋转的角度
downRotate(){
return 'rotate(' + 360 * this.rate + 'deg)'
},
// 文本提示
downText(){
switch (this.type){
case 1: return this.mOption.textInOffset;
case 2: return this.mOption.textOutOffset;
case 3: return this.mOption.textLoading;
case 4: return this.mOption.textLoading;
default: return this.mOption.textInOffset;
}
}
}
};
</script>
<style>
@import "./mescroll-down.css";
</style>
<!--空布局
可作为独立的组件, 不使用mescroll的页面也能单独引入, 以便APP全局统一管理:
import MescrollEmpty from '@/components/mescroll-uni/components/mescroll-empty.vue';
<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
-->
<template>
<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
<image v-if="icon" class="empty-icon" src="/static/nocontent-1.png" mode="widthFix" />
<view v-if="tip" class="empty-tip">{{ tip }}</view>
<view v-if="option.btnText" class="empty-btn" @click="emptyClick">{{ option.btnText }}</view>
</view>
</template>
<script>
// 引入全局配置
import GlobalOption from './../mescroll-uni-option.js';
export default {
props: {
// empty的配置项: 默认为GlobalOption.up.empty
option: {
type: Object,
default() {
return {
};
}
}
},
// 使用computed获取配置,用于支持option的动态配置
computed: {
// 图标
icon() {
return this.option.icon == null ? GlobalOption.up.empty.icon : this.option.icon; // 此处不使用短路求值, 用于支持传空串不显示图标
},
// 文本提示
tip() {
return this.option.tip == null ? GlobalOption.up.empty.tip : this.option.tip; // 此处不使用短路求值, 用于支持传空串不显示文本提示
}
},
methods: {
// 点击按钮
emptyClick() {
this.$emit('emptyclick');
}
}
};
</script>
<style>
/* 无任何数据的空布局 */
.mescroll-empty {
box-sizing: border-box;
width: 100%;
padding: 60% 50rpx;
text-align: center;
}
.mescroll-empty.empty-fixed {
z-index: 99;
position: absolute; /*transform会使fixed失效,最终会降级为absolute */
top: 100rpx;
left: 0;
}
.mescroll-empty .empty-icon {
width: 280rpx;
height: 280rpx;
}
.mescroll-empty .empty-tip {
font-size: 24rpx;
margin-top: -80rpx;
color: gray;
}
.mescroll-empty .empty-btn {
display: inline-block;
margin-top: 40rpx;
min-width: 200rpx;
padding: 18rpx;
font-size: 28rpx;
border: 1rpx solid #e04b28;
border-radius: 60rpx;
color: #e04b28;
}
.mescroll-empty .empty-btn:active {
opacity: 0.75;
}
</style>
<!-- 回到顶部的按钮 -->
<template>
<image
v-if="mOption.src"
class="mescroll-totop"
:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-safe-bottom': mOption.safearea}]"
:style="{'z-index':mOption.zIndex, 'left': left, 'right': right, 'bottom':addUnit(mOption.bottom), 'width':addUnit(mOption.width), 'border-radius':addUnit(mOption.radius)}"
:src="mOption.src"
mode="widthFix"
@click="toTopClick"
/>
</template>
<script>
export default {
props: {
// up.toTop的配置项
option: Object,
// 是否显示
value: false
},
computed: {
// 支付宝小程序需写成计算属性,prop定义default仍报错
mOption(){
return this.option || {}
},
// 优先显示左边
left(){
return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto';
},
// 右边距离 (优先显示左边)
right() {
return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right);
}
},
methods: {
addUnit(num){
if(!num) return 0;
if(typeof num === 'number') return num + 'rpx';
return num
},
toTopClick() {
this.$emit('input', false); // 使v-model生效
this.$emit('click'); // 派发点击事件
}
}
};
</script>
<style>
/* 回到顶部的按钮 */
.mescroll-totop {
z-index: 9990;
position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */
right: 20rpx;
bottom: 120rpx;
width: 72rpx;
height: auto;
border-radius: 50%;
opacity: 0;
transition: opacity 0.5s; /* 过渡 */
margin-bottom: var(--window-bottom); /* css变量 */
}
/* 适配 iPhoneX */
.mescroll-safe-bottom{
margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
}
/* 显示 -- 淡入 */
.mescroll-totop-in {
opacity: 1;
}
/* 隐藏 -- 淡出且不接收事件*/
.mescroll-totop-out {
opacity: 0;
pointer-events: none;
}
</style>
/* 上拉加载区域 */
.mescroll-upwarp {
min-height: 60rpx;
padding: 30rpx 0;
text-align: center;
clear: both;
}
/*提示文本 */
.mescroll-upwarp .upwarp-tip,
.mescroll-upwarp .upwarp-nodata {
display: inline-block;
font-size: 28rpx;
vertical-align: middle;
/* color: gray; 已在style设置color,此处删去*/
}
.mescroll-upwarp .upwarp-tip {
margin-left: 16rpx;
}
/*旋转进度条 */
.mescroll-upwarp .upwarp-progress {
display: inline-block;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid gray;
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
vertical-align: middle;
}
/* 旋转动画 */
.mescroll-upwarp .mescroll-rotate {
animation: mescrollUpRotate 0.6s linear infinite;
}
@keyframes mescrollUpRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
\ No newline at end of file
<!-- 上拉加载区域 -->
<template>
<view class="mescroll-upwarp" :style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="isUpLoading">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
<view class="upwarp-tip">{{ mOption.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
</view>
</template>
<script>
export default {
props: {
option: Object, // up的配置项
type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了)
},
computed: {
// 支付宝小程序需写成计算属性,prop定义default仍报错
mOption() {
return this.option || {};
},
// 加载中
isUpLoading() {
return this.type === 1;
},
// 没有更多了
isUpNoMore() {
return this.type === 2;
}
}
};
</script>
<style>
@import './mescroll-up.css';
</style>
page {
-webkit-overflow-scrolling: touch; /* 使iOS滚动流畅 */
}
.mescroll-body {
position: relative; /* 下拉刷新区域相对自身定位 */
height: auto; /* 不可固定高度,否则overflow: hidden, 可通过设置最小高度使列表不满屏仍可下拉*/
overflow: hidden; /* 遮住顶部下拉刷新区域 */
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
}
\ No newline at end of file
This diff is collapsed.
// mescroll-body 和 mescroll-uni 通用
// import MescrollUni from "./mescroll-uni.vue";
// import MescrollBody from "./mescroll-body.vue";
const MescrollMixin = {
// components: { // 非H5端无法通过mixin注册组件, 只能在main.js中注册全局组件或具体界面中注册
// MescrollUni,
// MescrollBody
// },
data() {
return {
mescroll: null //mescroll实例对象
}
},
// 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
onPullDownRefresh(){
this.mescroll && this.mescroll.onPullDownRefresh();
},
// 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
onPageScroll(e) {
this.mescroll && this.mescroll.onPageScroll(e);
},
// 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
onReachBottom() {
this.mescroll && this.mescroll.onReachBottom();
},
methods: {
// mescroll组件初始化的回调,可获取到mescroll对象
mescrollInit(mescroll) {
this.mescroll = mescroll;
this.mescrollInitByRef(); // 兼容字节跳动小程序
},
// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序: http://www.mescroll.com/qa.html?v=20200107#q26)
mescrollInitByRef() {
if(!this.mescroll || !this.mescroll.resetUpScroll){
let mescrollRef = this.$refs.mescrollRef;
if(mescrollRef) this.mescroll = mescrollRef.mescroll
}
},
// 下拉刷新的回调
downCallback() {
// mixin默认resetUpScroll
this.mescroll.resetUpScroll()
},
// 上拉加载的回调
upCallback() {
// mixin默认延时500自动结束加载
setTimeout(()=>{
this.mescroll.endErr();
}, 500)
}
},
mounted() {
this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
}
}
export default MescrollMixin;
// 全局配置
// mescroll-body 和 mescroll-uni 通用
const GlobalOption = {
down: {
// 其他down的配置参数也可以写,这里只展示了常用的配置:
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
textLoading: '加载中 ...', // 加载中的提示文本
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
},
up: {
// 其他up的配置参数也可以写,这里只展示了常用的配置:
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
offset: 80, // 距底部多远时,触发upCallback
isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
toTop: {
// 回到顶部按钮,需配置src才显示
src: "http://www.mescroll.com/img/mescroll-totop.png?v=1", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
},
empty: {
use: true, // 是否显示空布局
icon: "http://www.mescroll.com/img/mescroll-empty.png?v=1", // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
tip: '~ 暂无相关数据 ~' // 提示
}
}
}
export default GlobalOption
page {
height: 100%;
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
}
.mescroll-uni-warp{
height: 100%;
}
.mescroll-uni {
position: relative;
width: 100%;
height: 100%;
min-height: 200rpx;
overflow-y: auto;
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
}
/* 定位的方式固定高度 */
.mescroll-uni-fixed{
z-index: 1;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: auto; /* 使right生效 */
height: auto; /* 使bottom生效 */
}
This diff is collapsed.
This diff is collapsed.
/**
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期:
* 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例)
* 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例)
*/
const MescrollCompMixin = {
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
onPageScroll(e) {
let item = this.$refs["mescrollItem"];
if(item && item.mescroll) item.mescroll.onPageScroll(e);
},
onReachBottom() {
let item = this.$refs["mescrollItem"];
if(item && item.mescroll) item.mescroll.onReachBottom();
},
// 当down的native: true时, 还需传递此方法进到子组件
onPullDownRefresh(){
let item = this.$refs["mescrollItem"];
if(item && item.mescroll) item.mescroll.onPullDownRefresh();
}
}
export default MescrollCompMixin;
/**
* mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例)
*/
const MescrollMoreItemMixin = {
props:{
i: Number, // 每个tab页的专属下标
index: { // 当前tab的下标
type: Number,
default(){
return 0
}
}
},
data() {
return {
downOption:{
auto:false // 不自动加载
},
upOption:{
auto:false // 不自动加载
},
isInit: false // 当前tab是否已初始化
}
},
watch:{
// 监听下标的变化
index(val){
if (this.i === val && !this.isInit) {
this.isInit = true; // 标记为true
this.mescroll && this.mescroll.triggerDownScroll();
}
}
},
methods: {
// mescroll组件初始化的回调,可获取到mescroll对象
mescrollInit(mescroll) {
this.mescroll = mescroll;
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序 (mescroll-mixins.js)
// 自动加载当前tab的数据
if(this.i === this.index){
this.isInit = true; // 标记为true
this.mescroll.triggerDownScroll();
}
},
}
}
export default MescrollMoreItemMixin;
/**
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期:
* 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例)
* 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例)
*/
const MescrollMoreMixin = {
data() {
return {
tabIndex: 0 // 当前tab下标
}
},
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
onPageScroll(e) {
let mescroll = this.getMescroll(this.tabIndex);
mescroll && mescroll.onPageScroll(e);
},
onReachBottom() {
let mescroll = this.getMescroll(this.tabIndex);
mescroll && mescroll.onReachBottom();
},
// 当down的native: true时, 还需传递此方法进到子组件
onPullDownRefresh(){
let mescroll = this.getMescroll(this.tabIndex);
mescroll && mescroll.onPullDownRefresh();
},
methods:{
// 根据下标获取对应子组件的mescroll
getMescroll(i){
if(!this.mescrollItems) this.mescrollItems = [];
if(!this.mescrollItems[i]) {
// v-for中的refs
let vForItem = this.$refs["mescrollItem"];
if(vForItem){
this.mescrollItems[i] = vForItem[i]
}else{
// 普通的refs,不可重复
this.mescrollItems[i] = this.$refs["mescrollItem"+i];
}
}
let item = this.mescrollItems[i]
return item ? item.mescroll : null
},
// 切换tab,恢复滚动条位置
tabChange(i){
let mescroll = this.getMescroll(i);
if(mescroll){
// 延时(比$nextTick靠谱一些),确保元素已渲染
setTimeout(()=>{
mescroll.scrollTo(mescroll.getScrollTop(),0)
},30)
}
}
}
}
export default MescrollMoreMixin;
<template>
<view class="cu-form-group" style="z-index:10">
<view class="flex align-center">
<view class="title">
<text class="text-red" v-if="required">*</text>
<text space="ensp">{{label}}</text>
</view>
<picker
@change="pickerChange"
:range="selections"
:value="valueIndex"
:disabled="disabled">
<input
:placeholder="placeholder"
name="input"
v-model="selected"
:disabled="true"
></input>
</picker>
</view>
</view>
</template>
<script>
export default {
name: "AppSecelt",
behaviors: ['uni://form-field'],
props:{
display:{
type:String,
default:'inline-block',
required:false
},
placeholder:{
type:String,
default:'请选择',
required:false
},
label:{
type:String,
default:'',
required:false
},
value:{
type:String,
required:false
},
border:{
type:Boolean,
default:false,
required:false
},
dict:{
type:Array,
default:()=>[],
required:true
},
name:{
type:String,
default:'',
required:false
},
required:{
type:Boolean,
default:false,
required:false
},
disabled:{
type:Boolean,
default:false,
required:false
},
space:{
type:Boolean,
default:false,
required:false
}
},
data(){
return {
show:false,
selected:'',
valueIndex:0,
selections:[]
}
},
watch:{
value:{
immediate:true,
handler(val){
if(!val){
this.selected = ''
this.valueIndex = 0
}else{
this.dict.map((item,index)=>{
if(item.value == val){
this.selected = item.text;
this.valueIndex = index
}
})
}
}
},
dict(){
this.initSelections();
}
},
created(){
this.initSelections();
},
methods:{
initSelections(){
let arr = [];
this.dict.map(item=>{
arr.push(item.text)
});
this.selections = arr
},
pickerChange(e){
console.log("appselect::pickerChange",e.detail.value)
let backString = '';
let obj=this.dict[e.detail.value];
this.selected=obj.text;
backString=obj.value;
console.log("backString",backString)
console.log("this.selected",this.selected)
this.$emit('input',backString);
// #ifndef MP-WEIXIN
this.$emit('change',backString);
// #endif
}
},
model: {
prop: 'value',
event:'change'
}
}
</script>
<style scoped>
.cu-form-group uni-picker::after {
font-family: cuIcon;
display: block;
content: "\e6a3";
position: absolute;
font-size: 14px;
color: #FFFFFF;
line-height: 42px;
width: 25px;
text-align: center;
top: 0;
bottom: 0;
right: -8px;
margin: auto;
}
</style>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<template>
<view>
<view class="cu-custom" :style="[{height:CustomBar + 'px',zIndex:zIndex}]">
<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>
<view class="action">
<slot name="right"></slot>
</view>
</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: ''
},
zIndex:{
type: String,
default: '10'
},
backRouterName:{
type: String,
default: ''
}
},
methods: {
BackPage() {
if(!this.backRouterName){
uni.navigateBack({
delta: 1
});
}else{
this.$Router.replace({name:this.backRouterName})
}
}
}
}
</script>
<style>
</style>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File added
{
"lockfileVersion": 1
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment