Commit 86111281 authored by jyx's avatar jyx

代码优化

parent a9580391
var pageMixin = {
}
export default pageMixin;
\ No newline at end of file
// export default {
// created() {
// //#ifdef MP-WEIXIN
// wx.showShareMenu({
// withShareTicket: true,
// menus: ['shareAppMessage', 'shareTimeline']
// });
// //#endif
// },
// }
export default {
data() {
return {
share: {
title: '更多内容,等你来看',
imageUrl: 'https://mints-pkg.oss-cn-beijing.aliyuncs.com/pkg/img/book_bg_splash.png',
path: '/pages/loading'
// path: '/pages/loading?shareId=' + getApp().globalData.userId,
}
}
},
onShareAppMessage(res) { //发送给朋友
return {
title: this.share.title,
imageUrl: this.share.imageUrl,
path: this.share.path,
}
},
onShareTimeline(res) { //分享到朋友圈
return {
title: this.share.title,
imageUrl: this.share.imageUrl,
path: this.share.path,
}
},
}
\ No newline at end of file
import {
compareVersion
} from "../utils/util.js";
let NAVI_BAR_HEIGHT = 0;
let NAVI_HEIGHT = 0;
let BOTTOM_SAFE_HEIGHT = 0;
var systemInfoMixin = {
data: function() {
return {
bottomSafeHeight: 0, // 距底部安全高度
statusBarHeight: 0, // 状态栏高度
naviBarHeight: 0, // 导航栏 高度
naviHeight: 0, // navi 总体高度
windowHeight: 0, // 窗口高度
windowWidth: 0, // 窗口宽度
sdkVersion: "", // uni小程序SDK版本、小程序客户端基础库版本
}
},
mounted() {
const systemInfo = tt.getSystemInfoSync();
this.sdkVersion = systemInfo.hostSDKVersion;
this.windowWidth = this.dataCheck(systemInfo.windowWidth);
this.windowHeight = this.dataCheck(systemInfo.windowHeight);
this.statusBarHeight = this.dataCheck(systemInfo.statusBarHeight);
if (!BOTTOM_SAFE_HEIGHT) {
BOTTOM_SAFE_HEIGHT = this.dataCheck(systemInfo.screenHeight - systemInfo.safeArea.height - this
.statusBarHeight);
}
this.bottomSafeHeight = BOTTOM_SAFE_HEIGHT;
if (!NAVI_BAR_HEIGHT || !NAVI_HEIGHT) {
const custom = tt.getMenuButtonBoundingClientRect();
NAVI_BAR_HEIGHT = custom.height + (custom.top - this.statusBarHeight) * 2;
NAVI_HEIGHT = NAVI_BAR_HEIGHT + this.statusBarHeight;
}
this.naviBarHeight = this.dataCheck(NAVI_BAR_HEIGHT);
this.naviHeight = this.dataCheck(NAVI_HEIGHT ? NAVI_HEIGHT : statusBarHeight);
},
methods: {
// 版本检测,当前基础库及sdk版本是否大于目标版本
overstepSDKVersion(version) {
let result = compareVersion(this.sdkVersion, version);
return result == 1;
},
dataCheck(value) {
return value > 0 ? value : 0
}
}
}
export default systemInfoMixin;
\ No newline at end of file
import Label from "./Label";
export default class Book {
constructor(param) {
const {
id,
title,
avatar,
summary,
author,
isStick,
isOriginal,
isPublish,
content,
username,
userAvatar,
quantity,
commentCount,
likeCount,
collectCount,
isCollect,
commentContent,
categoryName,
categoryId,
createTime,
tagList,
isUnlock,
bookLegumes,
lockRate
} = param || {}
this.id = id;
this.title = title;
this.avatar = avatar || "/static/images/logo.png";
this.summary = summary;
this.tagList = tagList ? tagList.map(item => {
return new Label(item)
}).sort((a, b) => {
return a.sort - b.sort
}) : [];
this.author = author;
this.isStick = isStick;
this.isOriginal = isOriginal;
this.isPublish = isPublish;
this.content = content;
this.username = username;
this.userAvatar = userAvatar;
this.quantity = quantity;
this.commentContent = commentContent;
this.commentCount = commentCount
this.likeCount = likeCount;
this.collectCount = collectCount;
this.isCollect = isCollect;
this.categoryName = categoryName;
this.categoryId = categoryId;
this.createTime = createTime;
this.isUnlock = isUnlock;
this.bookLegumes = bookLegumes;
this.lockRate = lockRate;
}
}
\ No newline at end of file
export default class BookBeanPack{
constructor(param) {
const {
id,
title,
price,
legumesNumber,
giveNumber
} = param || {}
this.id = id;
this.title = title;
this.price = price;
this.legumesNumber = legumesNumber;
this.giveNumber = giveNumber;
}
}
\ No newline at end of file
import DictValue from "./DictValue";
export default class Dict {
constructor(param) {
const {
defaultValue,
list
} = param || {};
this.defaultValue = defaultValue;
this.list = list ? list.map(item => {
return new DictValue(item)
}) : []
}
}
\ No newline at end of file
export default class DictValue {
constructor(param) {
const {
id,
dictId,
label,
value,
sort,
} = param || {}
this.id = id;
this.dictId = dictId;
this.label = label;
this.value = value;
this.sort = sort;
}
}
\ No newline at end of file
export default class Label {
constructor(param) {
const {
id,
name,
type,
sort,
clickVolume
} = param || {}
this.id = id;
this.name = name;
this.type = type;
}
}
\ No newline at end of file
export default class OperateAction {
constructor(param) {
const {
type,
customPrefix,
color,
size = 20,
name,
backgroundColor,
openType,
disabled = false,
id,
fn
} = param || {}
this.type = type;
this.customPrefix = customPrefix;
this.color = color;
this.size = size;
this.name = name;
this.backgroundColor = backgroundColor;
this.openType = openType;
this.disabled = disabled;
this.id = id;
this.fn = fn;
}
}
\ No newline at end of file
export default class Pack {
constructor(param) {
const {
id,
title,
price,
originalPrice,
} = param || {}
this.id = id;
this.title = title;
this.originalPrice = originalPrice ? originalPrice.toFixed(2) : 0;
this.price = price ? price.toFixed(2) : 0;
this.cutDown = parseFloat(originalPrice ? originalPrice - price : 0).toFixed(2);
}
}
\ No newline at end of file
export default class PayInfo {
constructor(param) {
const {
timeStamp,
nonceStr,
prepay_id,
packageStr,
signType,
paySign
} = param || {}
this.timeStamp = timeStamp;
this.nonceStr = nonceStr;
this.prepay_id = prepay_id;
this.packageStr = packageStr;
this.signType = signType;
this.paySign = paySign;
}
}
\ No newline at end of file
export default class ReadCount {
constructor(param) {
const {
count
} = param || {}
this.count = count;
let result = initWithCount(count);
const {
hour,
minute,
second
} = result;
this.hour = hour;
this.minute = minute;
this.second = second;
}
}
function initWithCount(count) {
if (!count) return {};
let second = parseInt(count / 1000); // 秒数
let minute = parseInt(second / 60); // 分钟数
second = second % 60; // 更新秒数
let hour = parseInt(minute / 60); // 小时数
minute = minute % 60; // 更新分钟数
return {
hour,
minute,
second
}
}
\ No newline at end of file
export default class User {
constructor(param) {
const {
id,
name,
phone,
sign,
avater,
nickName,
roleId,
sex,
status,
memberFlag,
memberExpirationDate,
bookLegumes
} = param || {}
this.id = id;
this.name = name || "微信用户";
this.phone = phone;
this.sign = sign || '书中自有颜如玉,书中自有黄金屋,书中自有千钟黍';
this.avater = '/static/images/logo.png';
this.nickName = nickName || "微信用户";
this.roleId = roleId;
this.sex = sex;
this.status = status;
this.memberFlag = memberFlag;
this.memberExpirationDate = memberExpirationDate;
this.bookLegumes = bookLegumes;
}
isVip() {
if (this.memberFlag) {
let cDate = new Date();
cDate = cDate.getTime();
return this.memberExpirationDate >= cDate;
}
return false;
}
isVipExpire() {
if (this.memberFlag) {
let cDate = new Date();
cDate = cDate.getTime();
return this.memberExpirationDate < cDate
}
return false
}
}
\ No newline at end of file
const {
postNotification,
addNormalNotificationObserver,
removeNotificationObserver
} = require("../utils/notificationCenter");
import Dict from "../models/Dict.js";
import {
apiPOST,
apiGET
} from "../utils/apiRequest.js"
import {
readStorage,
saveStorage
} from "../utils/storageUtil.js";
import {
dateFormat,
stringToDate
} from "../utils/timeUtil.js";
import {
isEmpty
} from "../utils/util.js";
/** ================= 阅读时间 ================= */
let READ_TIME_COUNT_MAP = null;
let KEY_STORAGE_READ_TIME_COUNT = "READ_TIME_COUNT";
/**
* 读取阅读时间计时
*/
function getReadTimeCount() {
if (!READ_TIME_COUNT_MAP) {
READ_TIME_COUNT_MAP = readStorage(KEY_STORAGE_READ_TIME_COUNT) || {}
}
let date = dateFormat(new Date(), "yyyy-MM-dd");
let count = READ_TIME_COUNT_MAP[date] || 0;
return count;
}
/**
* 存储阅读时间计时
*/
function setReadTimeCount(count) {
if (!READ_TIME_COUNT_MAP) {
READ_TIME_COUNT_MAP = readStorage(KEY_STORAGE_READ_TIME_COUNT) || {}
}
let date = dateFormat(new Date(), "yyyy-MM-dd");
let result = READ_TIME_COUNT_MAP[date] || 0;
result = result + count;
READ_TIME_COUNT_MAP[date] = result;
saveStorage(KEY_STORAGE_READ_TIME_COUNT, READ_TIME_COUNT_MAP);
}
let startReadTime = null; // 开始阅读时间
/**
* 开始阅读计时
*/
function startCountReadTime() {
startReadTime = new Date();
}
/**
* 停止阅读计时
*/
function endCountReadTime() {
if (!startReadTime) return;
let endReadTime = new Date();
let start = dateFormat(startReadTime, "yyyy-MM-dd");
start = start.split("-");
let end = dateFormat(endReadTime, "yyyy-MM-dd");
end = end.split("-");
let isSameDay = true;
start.forEach((item, index) => {
if (item != end[index]) {
isSameDay = false;
}
})
let count = 0;
if (isSameDay) {
count = endReadTime.getTime() - startReadTime.getTime();
} else {
let dayStart = stringToDate(end.join("-"))
count = endReadTime.getTime() - dayStart.getTime();
}
startReadTime = null;
setReadTimeCount(count);
}
/** ================= 收藏 ================= */
const KEY_NOTIFICATION_COLLECTION_CHANGE = 'COLLECTION_CHANGE';
/**
* 收藏列表变动通知
* @param {Object} bookId
* @param {Object} isCollect
*/
function noticeCollectionListChange(bookId, isCollect) {
postNotification(KEY_NOTIFICATION_COLLECTION_CHANGE, {
bookId,
isCollect
});
}
/**
* 收藏列表变动监听
* @param {Object} fn
* @param {Object} observer
*/
function watchCollectionChange(fn, observer) {
addNormalNotificationObserver(KEY_NOTIFICATION_COLLECTION_CHANGE, fn, observer);
}
/**
* 移除收藏列表变动监听
* @param {Object} observer
*/
function removeCollectionChangeWatch(observer) {
removeNotificationObserver(KEY_NOTIFICATION_COLLECTION_CHANGE, observer);
}
/** ================= 字典 ================= */
const DictMap = {}; // 系统字典缓存数据
const ENUM_DICT_NAME = {
SEX: "sys_user_sex"
}
/**
* 获取系统字典
*/
function getSystemDict(types = [], callback) {
let result = {};
let emptyKeys = [];
types.map(key => {
let value = DictMap[key];
if (value) {
result[key] = value;
} else {
emptyKeys.push(key);
}
})
if (isEmpty(emptyKeys)) {
if (typeof callback == 'function') callback(true, result);
return;
}
apiPOST({
url: `/system/dictData/getDataByDictType`,
data: emptyKeys,
callback: (success, data) => {
if (success) {
Object.keys(data).map(key => {
DictMap[key] = new Dict(data[key])
});
getSystemDict(types, callback);
} else {
if (typeof callback == 'function') callback(false)
}
}
})
}
/**
* 收藏、取消收藏书籍
* @param {Object} isCollected
* @param {Object} bookId
* @param {Object} callback
*/
function collectionBook(isCollected, bookId, callback) {
let url = isCollected ? '/readSystem/v1/collect/collect' : "/readSystem/v1/collect/cancel";
url = `${url}?articleId=${bookId}`
apiGET({
url,
callback
})
}
/** 获取充值入口开关
* @param {Object} callback
*/
function getOpens(callback) {
apiGET({
url: `/v1/article/opens`,
callback
})
}
/**
* 获取VIP套餐列表
* @param {Object} callback
*/
function getPackData(callback) {
apiPOST({
url: `/vip/getVipProducts/point`,
callback
})
}
/**
* 提交反馈
*/
function getFeedback(text, callback) {
apiPOST({
url: `/v1/feedback/`,
data: {
title: '反馈',
content: text,
type: 1
},
callback
})
}
/**
* 获取openId
* @param {Object} code wx code
* @param {Object} callback
*/
function getOpenId(code, callback) {
apiPOST({
url: `/getOpenId`,
data: {
code
},
callback
})
}
/**
* 支付类型
*/
const ENUM_PAY_TYPE = {
VIP: {
value: 1,
name: "购买会员"
},
BEAN: {
value: 2,
name: "购买书豆"
}
}
/**
* 获取开通VIP支付信息
* @param {Object} id
* @param {Object} amount
* @param {Object} openId
* @param {Object} callback
*/
function getPayInfo(id, amount, openId, callback, typeId = ENUM_PAY_TYPE.VIP.value) {
apiPOST({
url: `/wx/pay/createOrder`,
data: {
typeId,
memberTypeId: id,
amount: Number(amount),
openId
},
callback
})
}
function getPayInfoDy(pid) {
apiPOST({
url: `/vip/getVipPayParams/douyin`,
data: {
pid: pid
},
callback
})
}
/**
* 获取书豆套餐数据
* @param {Object} callback
*/
function getBookBeanPackData(callback) {
apiPOST({
url: `//system/bookLegumesType/list`,
callback
})
}
/**
* 解锁书籍
* @param {Object} bookId
* @param {Object} callback
*/
function buyBookWithBookBean(bookId, callback) {
apiPOST({
url: `/system/bookUser/create`,
data: {
articleId: bookId
},
callback
})
}
module.exports = {
getOpens,
getReadTimeCount,
startCountReadTime,
endCountReadTime,
noticeCollectionListChange,
watchCollectionChange,
removeCollectionChangeWatch,
collectionBook,
ENUM_DICT_NAME,
getSystemDict,
getBookBeanPackData,
getPackData,
getOpenId,
getFeedback,
ENUM_PAY_TYPE,
getPayInfoDy,
getPayInfo,
buyBookWithBookBean,
}
\ No newline at end of file
const {
ENUM_SEARCH_TYPE,
ENUM_MESSAGE_PAGE_TYPE
} = require("../../static/enums/enum_value");
// 搜索页面
function gotoBookSearchPage(searchType = ENUM_SEARCH_TYPE.WAREHOUSE, keyword) {
uni.navigateTo({
url: `/page-subs/sub_A/book-search/book-search`,
success: (res) => {
res.eventChannel.emit("openBookSearchPage", {
keyword,
searchType
})
}
})
}
// 文章详情
function gotoBookContentPage(bookId) {
uni.navigateTo({
url: `/page-subs/sub_A/book-content/book-content`,
success: (res) => {
res.eventChannel.emit("openBookContentPage", {
bookId
})
}
})
}
// 文章封面
function gotoBookCoverPage(bookId) {
return;
uni.navigateTo({
url: `/page-subs/sub_A/book-cover/book-cover`,
success: (res) => {
res.eventChannel.emit("openBookCoverPage", {
bookId
})
}
})
}
// 阅读历史
function gotoReadHistoryPage() {
uni.navigateTo({
url: `/page-subs/sub_A/read-history/read-history`
})
}
// vip申请
function gotoVIPApplyPage() {
uni.navigateTo({
url: `/page-subs/sub_B/vip-apply/vip-apply`,
})
}
// 用户编辑
function gotoUserEditPage(userInfo) {
uni.navigateTo({
url: `/page-subs/sub_B/user-edit/user-edit`,
success: (res) => {
res.eventChannel.emit("openUserEditPage", {
userInfo
})
}
})
}
// 消息
function gotoMessagePage(type = ENUM_MESSAGE_PAGE_TYPE.SYS.value) {
uni.navigateTo({
url: `/page-subs/sub_B/message/message`,
success: (res) => {
res.eventChannel.emit("openMessagePage", {
type
})
}
})
}
// 阅读喜好
function gotoReadPreferencePage() {
uni.navigateTo({
url: `/page-subs/sub_B/read-preference/read-preference`,
})
}
// 书豆明细
function gotoBookBeanDetailPage() {
uni.navigateTo({
url: `/page-subs/sub_B/book-bean-detail/book-bean-detail`
})
}
// 书豆充值
function gotoBookBeanRechargePage() {
uni.navigateTo({
url: `/page-subs/sub_B/book-bean-recharge/book-bean-recharge`
})
}
module.exports = {
/** sub_A */
gotoBookSearchPage,
gotoBookContentPage,
gotoBookCoverPage,
gotoReadHistoryPage,
/** sub_B */
gotoVIPApplyPage,
gotoUserEditPage,
gotoMessagePage,
gotoReadPreferencePage,
gotoBookBeanDetailPage,
gotoBookBeanRechargePage
}
\ No newline at end of file
const {
postNotification,
addNormalNotificationObserver,
removeNotificationObserver
} = require("../utils/notificationCenter")
import {
KEY_NOTIFICATION_LOGIN_SHOW,
KEY_NOTIFICATION_LOGIN_SUCCESS
} from "../../static/keys/notification-keys.js"
import {
readStorage,
saveStorage
} from "../utils/storageUtil.js";
import User from "../models/User.js";
import {
isEmpty,
isNotEmpty
} from "../utils/util.js";
import {
printError
} from "../utils/printUtil.js";
import config from "../../config/index.js";
import {
toastMessage,
toastHide
} from "../utils/toastUtil.js";
// 请求成功处理
function successHandler(url, param, res, callback) {
let tokenExpireFn = () => {
toastHide();
if (uni.getStorageSync('showLoginModal')) {
return
}
uni.setStorageSync('showLoginModal', true)
uni.showModal({
title: "登录状态已过期",
content: "当前登录状态已过期,请重新登录!",
cancelText: "暂不登录",
confirmText: "前往登录",
success: (res) => {
if (res.confirm) {
showLoginView();
} else {
logout(() => {}, true);
}
uni.setStorageSync('showLoginModal', false)
}
})
}
if (res.status != 200) {
toastMessage(`服务器异常,${res.status}`)
printError("请求失败", url, param, res.status, res.errMsg);
if (typeof callback == 'function') callback(false, res.errMsg)
} else {
if (res.data.code == 200) {
if (typeof callback == 'function') callback(true, res.data.data)
} else if (res.data.code == 401) {
tokenExpireFn()
if (typeof callback == 'function') callback(false, res.data.message);
} else {
toastMessage(`服务器异常,${res.data.code}`)
printError("请求失败", url, param, res.data.code);
if (typeof callback == 'function') callback(false, res.data.code)
}
}
}
// 请求失败处理
function failHandler(url, param, error, callback) {
toastMessage("request fail~");
printError("请求--失败", url, param, error.errMsg);
if (typeof callback == 'function') callback(false, error.errMsg)
}
// 登录
function login(data, callback) {
requestToken(data, (success, result) => {
if (success) {
saveToken(result.token); // 存储token
refreshUserInfo(); // 更新用户数据
setTimeout(() => {
uni.reLaunch({
url: `/pages/warehouse/warehouse?bookId=` + data.bookId,
})
}, 1400);
}
})
}
// 更新用户数据
function refreshUserInfo() {
requestUserInfo((success, userData) => {
if (success) {
let user = new User({
...userData,
name: userData.username,
nickName: userData.nickname,
avater: userData.avatar,
})
saveNickname(userData.nickname);
saveUserInfo(user); // 存储用户数据
postNotification(KEY_NOTIFICATION_LOGIN_SUCCESS); // 通知登录成功
}
})
}
// 绑定手机号
function postPhone(data, callback) {
let url = `${config["BASE_URL"]}/info/postPhone`;
let header = {
Authorization: `${readToken()}`
}
uni.request({
url,
header,
data,
method: "POST",
success: (res) => {
if (res.data.code == 200) {
callback(true, "")
} else {
callback(false, "")
}
},
fail: (error) => {
callback(false, "")
}
})
}
// 请求token
function requestToken(data, callback) {
let url = `${config["BASE_URL"]}/user/ttLogin`;
let header = {
pkgName: 'com.duben.dybookhm',
token: ``
}
uni.request({
url,
data,
header,
method: "POST",
success: (res) => {
successHandler(url, data, res, callback)
},
fail: (error) => {
failHandler(url, data, error, callback);
}
})
}
// 请求用户数据
function requestUserInfo(callback) {
let url = `${config['BASE_URL']}/system/user/getCurrentUserInfo`;
let header = {
Authorization: `${readToken()}`
}
uni.request({
url,
header,
method: "POST",
success: (res) => {
successHandler(url, {}, res, callback);
},
fail: (error) => {
failHandler(url, data, error, callback);
}
})
}
// 退出登录
function logout(callback, exprireOut = false) {
// let clearInfoFn = (msg) => {
// if (msg) {
// toastMessage(msg);
// }
// saveUserInfo(null);
// saveToken(null)
// }
// if (exprireOut) {
// clearInfoFn();
// if (typeof callback == 'function') callback(true);
// } else {
// let url = `${config['BASE_URL']}/logout`;
// let header = {
// Authorization: `${readToken()}`
// }
// uni.request({
// url,
// header,
// method: "GET",
// success: (res) => {
// successHandler(url, {}, res, (success, data) => {
// if (success) {
// clearInfoFn(data);
// }
// if (typeof callback == 'function') callback(success, data);
// });
// },
// fail: (error) => {
// failHandler(url, data, error, callback);
// }
// })
// }
}
const KEY_STORAGE_TOKEN = "TOKEN"
let LOCAL_TOKEN;
function saveToken(token) {
LOCAL_TOKEN = token;
saveStorage(KEY_STORAGE_TOKEN, token);
}
function readToken() {
if (!LOCAL_TOKEN) {
LOCAL_TOKEN = readStorage(KEY_STORAGE_TOKEN)
}
return LOCAL_TOKEN;
}
const KEY_STORAGE_NICKNAME = "NICKNAME"
let LOCAL_NICKNAME;
function saveNickname(nickname) {
LOCAL_NICKNAME = nickname;
saveStorage(KEY_STORAGE_NICKNAME, nickname);
}
function readNickname() {
if (!LOCAL_NICKNAME) {
LOCAL_NICKNAME = readStorage(KEY_STORAGE_NICKNAME)
}
return LOCAL_NICKNAME;
}
const KEY_STORAGE_USERINFO = "USERINFO";
const KEY_NOTIFICATION_USERINFO_CHANGE = 'USERINFO_CHANGE';
function saveUserInfo(userInfo) {
if (userInfo) {
let result = readUserInfo() || {};
Object.keys(userInfo).forEach(key => {
result[key] = (userInfo[key] == null || userInfo[key] == undefined) ? result[key] : userInfo[key]
})
USERINFO = new User(result);
} else {
USERINFO = null;
}
saveStorage(KEY_STORAGE_USERINFO, USERINFO)
postNotification(KEY_NOTIFICATION_USERINFO_CHANGE, {
userInfo: USERINFO
})
}
let USERINFO = null;
function readUserInfo() {
if (isEmpty(USERINFO)) {
USERINFO = readStorage(KEY_STORAGE_USERINFO);
}
USERINFO = isNotEmpty(USERINFO) ? new User(USERINFO) : null;
return USERINFO;
}
function watchUserInfoChange(fn, observer) {
if (typeof fn == 'function') fn.call(observer, {
userInfo: readUserInfo()
});
addNormalNotificationObserver(KEY_NOTIFICATION_USERINFO_CHANGE, (info) => {
if (typeof fn == 'function') fn.call(observer, {
userInfo: info.userInfo
})
}, observer)
}
function removeUserInfoChangeWatch(observer) {
removeNotificationObserver(KEY_NOTIFICATION_USERINFO_CHANGE, observer);
}
// 展示登录弹窗
function showLoginView() {
postNotification(KEY_NOTIFICATION_LOGIN_SHOW, {
show: true
})
}
module.exports = {
login,
refreshUserInfo,
postPhone,
requestToken,
requestUserInfo,
logout,
showLoginView,
saveUserInfo,
readUserInfo,
readNickname,
watchUserInfoChange,
removeUserInfoChangeWatch,
saveToken,
readToken,
}
\ No newline at end of file
This diff is collapsed.
const KEY_STORAGE_CACHE_DATA = "CACHE_DATA";
import {
readStorage,
saveStorage
} from "./storageUtil";
import {
isEmpty
} from "./util";
const Max_Cache_Data_Size = 1024 * 1024 * 30; // 以字节为单位 30M
let Cache_Data = null; // {size, data: {key: {size, filePath, createTime}}}
// 获取缓存数据
function getCacheDataMap() {
if (!Cache_Data) {
let storageData = readStorage(KEY_STORAGE_CACHE_DATA);
if (isEmpty(storageData)) {
updateStorageCacheData({
size: 0,
data: {}
})
} else {
Cache_Data = storageData;
}
}
return Cache_Data;
}
// 移除缓存数据
function removeStorageCacheData(key, callback) {
let cacheData = getCacheDataMap();
const keyData = cacheData.data[key]; // 对应key 是否有数据
if (keyData) {
// 删除文件
uni.removeSavedFile({
filePath: keyData.filePath,
success: (res) => {
cacheData.size -= keyData.size; // 总尺寸缩减
delete cacheData.data[key]; // 移除数据记录
updateStorageCacheData(cacheData); // 更新缓存数据表
if (typeof callback == 'function') callback();
},
fail: (error) => {
throw new Error(`文件删除失败${error.errMsg}=>${keyData.filePath}`)
}
})
} else {
if (typeof callback == 'function') callback();
}
}
/**
* 新增缓存数据
* @param {Object} key key
* @param {Object} data 数据
*/
function appendStorageCacheData(key, data) {
// 先删除原有重叠缓存数据
removeStorageCacheData(key, () => {
let cacheData = getCacheDataMap();
cacheData.data[key] = data;
cacheData.size += data.size;
updateStorageCacheData(cacheData);
reduceCacheData();
});
}
/**
* 缓存数据超出缓存数据大小限制后,需要缩减缓存数据表,按照存储时间先进先出
*/
function reduceCacheData() {
let cacheData = getCacheDataMap();
let dataList = [];
Object.keys(cacheData.data).forEach(key => {
dataList.push({
...cacheData.data[key],
key
})
})
dataList.sort((a, b) => {
return a.createTime - b.createTime
})
if (cacheData.size > Max_Cache_Data_Size && cacheData.data.length > 1) {
removeStorageCacheData(dataList[0].key, () => {
reduceCacheData()
})
}
}
/**
* 更新缓存
* @param {Object} cacheData
*/
function updateStorageCacheData(cacheData) {
Cache_Data = cacheData;
saveStorage(KEY_STORAGE_CACHE_DATA, cacheData);
}
function cacheData(param = {
key,
url,
suffix,
callback
}) {
const {
key,
url,
suffix = "",
callback
} = param
if (!key || !url) throw new Error("cacheData key|url 不能为空");
let currentCache = getCacheDataMap();
let cacheDataFilePath = currentCache.data[key] ? currentCache.data[key].filePath : null;
if (cacheDataFilePath) { // 有记录
// 重新缓存,针对有记录,但是没有查找到文件的情况,为保证安全,先删除,再存储
let reCache = () => {
removeStorageCacheData(key, () => {
cacheData({
key,
url,
suffix,
callback
});
});
}
// 查文件,因为可能被清除了。
uni.getFileInfo({
filePath: cacheDataFilePath,
success: (res) => {
// 文件有
if (res.size > 0) { // 尺寸是否大于0 大于说明真实存在,返回缓存的地址
if (typeof callback == "function") callback(cacheDataFilePath);
} else { // 否则移除记录,重新存储
reCache();
}
},
fail: (error) => {
reCache();
}
})
return;
}
let filePath;
// #ifdef MP
filePath = `${wx.env.USER_DATA_PATH}/${key}${suffix?"."+suffix:suffix}`;
// #endif
// #ifndef MP
filePath = `${key}${suffix?"."+suffix:suffix}`;
// #endif
// 下载并保存文件,成功后记录
// 下载失败, 返回元数据
let downloadFailFn = (filePath) => {
if (typeof callback == "function") callback(filePath);
}
// 下载成功, 保存文件
let downloadSuccessFn = (tempFilePath) => {
uni.saveFile({
tempFilePath,
success: (res) => {
saveFileSuccessFn(res.savedFilePath);
},
fail: (error) => {
saveFileFail(tempFilePath)
}
})
}
// 保存失败 返回元数据
let saveFileFail = (filePath) => {
if (typeof callback == 'function') callback(filePath);
}
// 保存成功,计入表格
let saveFileSuccessFn = (filePath) => {
uni.getSavedFileInfo({
filePath,
success: (res) => {
appendStorageCacheData(key, {
filePath,
size: res.size,
createTime: res.createTime
})
if (typeof callback == 'function') callback(filePath)
}
})
}
// 下载文件
uni.downloadFile({
url,
filePath,
success: (res) => {
if (res.status == 200) {
downloadSuccessFn(res.tempFilePath ? res.tempFilePath : res.filePath);
} else {
downloadFailFn(url)
}
},
fail: (error) => {
downloadFailFn(url)
}
})
}
module.exports = {
cacheData, // 缓存数据
getCacheDataMap, // 获取缓存数据查询表
}
\ No newline at end of file
export const DUTY_CHAIN_NEXT_RESULT = "NEXT_CHAIN"
/**
* 创建职责链链条
* @param {Object} fn 链条方法
*/
let DutyChain = function(fn) {
this.fn = fn;
this.nextChain = null;
}
/**
* 插入下一链条职责
* @param {Object} nextChain 下一链条
*/
DutyChain.prototype.setNextChain = function(nextChain) {
return this.nextChain = nextChain;
}
/**
* 执行链条职责
*/
DutyChain.prototype.passRequest = function() {
let result = this.fn.apply(this, arguments); // 执行
// 执行结果 如果 == DUTY_CHAIN_NEXT_RESULT,执行下一链条,如果不是,返回执行结果
return result != DUTY_CHAIN_NEXT_RESULT ? result : this.nextChain && this.nextChain.passRequest.apply(this
.nextChain, arguments);
}
/**
* 手动触发下一链条,异步职责链无法同步返回执行结果时,在结果回调中手动调用该方法执行下一链条
*/
DutyChain.prototype.next = function() {
return this.nextChain && this.nextChain.passRequest.apply(this.nextChain, arguments);
}
export default DutyChain;
const ENUM_VALIDATOR_RULE = {
IS_NOT_EMPTY: "isNotEmpty",
IS_MOBILE: "isMobile",
IS_LAND_LINE: "isLandLine",
IS_ID_CARD: "isIDCard",
MIN_LENGTH: "minLength",
MAX_LENGTH: "maxLength",
IS_SAFE_PASSWORD: "isSafePassword",
IS_NUMBER: "isNumber",
IS_STRING: "isString",
IS_ENCHAR: "isENChar",
IS_NULL: "isNull"
}
const ENUM_VALIDATOR_TYPE = {
OR: "or",
AND: "and"
}
module.exports = {
ENUM_VALIDATOR_RULE,
ENUM_VALIDATOR_TYPE
}
\ No newline at end of file
This diff is collapsed.
import config from "../../config/index";
const {
PrintLevels: {
INFO,
DEBUG,
ERROR
}
} = require("../../config/configEnum");
import {
addNormalNotificationObserver,
postNotification,
removeNotificationObserver
} from "./notificationCenter";
import {
dateFormat,
getDateModel
} from "./timeUtil";
const KEY_NOTIFICATION_APPEND_LOG = "notification_key_append_log";
const KEY_NOTIFICATION_OPEN_LOG = "notification_key_open_log";
const KEY_STORAGE_OPEN_LOG = "open_log";
/** info 打印 */
function printInfo(...args) {
if (config["Print_Level"] <= INFO) {
console.log("INFO =>", ...args);
}
if (isLogOpen()) {
recordLog(INFO, ...args);
}
}
/** debug 打印 */
function printDebug(...args) {
if (config["Print_Level"] <= DEBUG) {
console.debug("DEBUG =>", ...args);
}
if (isLogOpen()) {
recordLog(DEBUG, ...args);
}
}
/** error 打印 */
function printError(...args) {
if (config["Print_Level"] <= ERROR) {
console.error("ERROR =>", ...args);
}
if (isLogOpen()) {
recordLog(ERROR, ...args);
}
}
/** ========================= 日志记录 ========================== */
class LogModel {
constructor(level, log) {
this.date = getDateModel(new Date());
this.level = level;
this.log = log;
}
getLevelName() {
let s = {
[`${INFO}`]: 'INFO',
[`${DEBUG}`]: 'DEBUG',
[`${ERROR}`]: 'ERROR',
}
return s[this.level];
}
getLogStr() {
return JSON.stringify(this.log);
}
getLogTime() {
return dateFormat(this.date.date, "yyyy-MM-dd HH:mm:ss.S")
}
}
function openLog(open) {
uni.setStorageSync(KEY_STORAGE_OPEN_LOG, open);
if (!open) {
DEBUG_INFO_LIST = [];
}
postNotification(KEY_NOTIFICATION_OPEN_LOG, open);
}
function watchLogOpen(callback, observer) {
if (typeof callback == "function") callback(uni.getStorageSync(KEY_STORAGE_OPEN_LOG));
addNormalNotificationObserver(KEY_NOTIFICATION_OPEN_LOG, (open) => {
if (typeof callback == 'function') callback(open)
}, observer);
}
function stopWatchLogOpen(observer) {
removeNotificationObserver(KEY_NOTIFICATION_OPEN_LOG, observer);
}
function isLogOpen() {
return uni.getStorageSync(KEY_STORAGE_OPEN_LOG)
}
let DEBUG_INFO_LIST = [];
/** 记录 debug 页面数据 */
function recordLog(level, ...args) {
let log = new LogModel(level, args);
DEBUG_INFO_LIST.unshift(log);
postNotification(KEY_NOTIFICATION_APPEND_LOG, DEBUG_INFO_LIST);
}
function watchDebugInfoList(callback, observer) {
if (typeof callback == 'function') callback(DEBUG_INFO_LIST);
addNormalNotificationObserver(KEY_NOTIFICATION_APPEND_LOG, () => {
if (typeof callback == 'function') callback(DEBUG_INFO_LIST);
}, observer)
}
function stopWatchDebugInfoList(observer) {
removeNotificationObserver(KEY_NOTIFICATION_APPEND_LOG, observer);
}
module.exports = {
/** print */
printInfo,
printDebug,
printError,
/** log */
openLog,
watchLogOpen,
stopWatchLogOpen,
isLogOpen,
recordLog,
watchDebugInfoList,
stopWatchDebugInfoList,
}
\ No newline at end of file
const {
printError
} = require("./printUtil")
function saveStorage(key, value) {
try {
uni.setStorageSync(key, value)
} catch (error) {
printError("save storage 错误", error.errMsg, key, value);
}
}
function readStorage(key) {
try {
return uni.getStorageSync(key);
} catch (error) {
printError("read storage 错误", error.errMsg, key);
}
}
function removeStorage(key) {
try {
return uni.removeStorageSync(key);
} catch (error) {
printError("remove storage 错误", error.errMsg, key);
}
}
module.exports = {
saveStorage,
readStorage,
removeStorage
}
const MIN_DAY_OF_WEEK = 0; // 星期最小值 星期日
const MAX_DAY_OF_WEEK = 6; // 星期最大值 星期六
/** 星期 值 处理, 首尾循环 */
function processDayOfWeek(dayOfWeek) {
if (dayOfWeek < MIN_DAY_OF_WEEK) {
dayOfWeek = MAX_DAY_OF_WEEK;
} else if (dayOfWeek > MAX_DAY_OF_WEEK) {
dayOfWeek = MIN_DAY_OF_WEEK;
}
return dayOfWeek;
}
/**
* 获取格式化时间
* @param date 时间
* @param format 格式
* 将 Date 转化为指定格式的String
* 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
* 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)可以用 1-2 个占位符
* eg:
* (new Date()).pattern("yyyy-MM-dd hh:mm:ss.S")==> 2006-07-02 08:09:04.423
* (new Date()).pattern("yyyy-MM-dd E HH:mm:ss") ==> 2009-03-10 二 20:09:04
* (new Date()).pattern("yyyy-MM-dd EE hh:mm:ss") ==> 2009-03-10 周二 08:09:04
* (new Date()).pattern("yyyy-MM-dd EEE hh:mm:ss") ==> 2009-03-10 星期二 08:09:04
* (new Date()).pattern("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
*/
function dateFormat(date, format = 'yyyy-MM-dd') {
var o = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours() % 12 == 0 ? 12 : date.getHours() % 12, //小时
"H+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"S": date.getMilliseconds() //毫秒
};
var week = {
"0": "日",
"1": "一",
"2": "二",
"3": "三",
"4": "四",
"5": "五",
"6": "六"
};
if (/(y+)/.test(format)) {
format = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
}
if (/(E+)/.test(format)) {
format = format.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? "星期" : "周") : "") + week[
date.getDay() + ""]);
}
for (var k in o) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k])
.length)));
}
}
return format;
}
/**
* 获取时间
* @param targetDate 目标时间 可以为空 Date 类型 或者 毫秒
* @param yearDuration 年份差距 数字类型 可以为空
* @param monthDuration 月份差距 数字类型 可以为空
* @param weekDuration 周差距 数字类型 可以为空
* @param dayDuration 天差距 数字类型 可以为空
* @param hourDuration 小时差距 数字类型 可以为空
* @param minDuration 分钟差距 数字类型 可以为空
* @param secDuration 秒数差距 数字类型 可以为空
*/
function getDate({
targetDate,
yearDuration,
monthDuration,
weekDuration,
dayDuration,
hourDuration,
minDuration,
secDuration
}) {
let tempDate = new Date();
if (targetDate != null) {
tempDate = new Date(targetDate);
}
if (yearDuration != null) {
tempDate.setFullYear(tempDate.getFullYear() * 1 + parseInt(yearDuration));
}
if (monthDuration != null) {
tempDate.setMonth(tempDate.getMonth() * 1 + parseInt(monthDuration));
}
if (weekDuration != null) {
tempDate.setDate(tempDate.getDate() * 1 + (7 * parseInt(weekDuration)));
}
if (dayDuration != null) {
tempDate.setDate(tempDate.getDate() * 1 + parseInt(dayDuration));
}
if (hourDuration != null) {
tempDate.setHours(tempDate.getHours() * 1 + parseInt(hourDuration));
}
if (minDuration != null) {
tempDate.setMinutes(tempDate.getMinutes() * 1 + parseInt(minDuration));
}
if (secDuration != null) {
tempDate.setSeconds(tempDate.getSeconds() * 1 + parseInt(secDuration));
}
return tempDate;
}
/**
* 日期时间 string 转 date
* @param {string} dateStr 日期时间
*/
function stringToDate(dateStr) {
if (!dateStr) return null;
return new Date(Date.parse(dateStr.replace(/-/g, "/")))
}
/** 获取相差分钟 */
function getMinDis(startDate, endDate) {
let startTime = stringToDate(startDate);
let endTime = stringToDate(endDate);
return parseInt(Math.abs(endTime - startTime)) / 1000 / 60
}
/** 获取相差小时数 */
function getHourDis(startDate, endDate) {
let startTime = stringToDate(startDate);
let endTime = stringToDate(endDate);
return parseInt(Math.abs(endTime - startTime)) / 1000 / 60 / 60
}
/** 获取天数相差多少天 */
function getDayDis(startDay, endDay) {
let startDate = stringToDate(startDay);
let endDate = stringToDate(endDay);
return parseInt(Math.abs(endDate - startDate)) / 1000 / 60 / 60 / 24;
}
/** 获取日期对象 */
function getDateModel(date, fullDateFormat = 'yyyy-MM-dd') {
return {
date,
time: dateFormat(date, "HH:mm"),
fullDay: dateFormat(date, fullDateFormat),
day: dateFormat(date, "MM.dd"),
week: dateFormat(date, "EE"),
weekDayIndex: date.getDay(),
}
}
/**
* 获取一周中的指定时间
* @param {number} week 0~6
* @param {string} time "hh:mm"
* @returns 返回一周中指定星期几的时间
*/
function getAssignWeekTime(week, time) {
let currentDate = new Date();
let currentWeek = currentDate.getDay();
let weekDis = week - currentWeek;
let newDate = getDate({
targetDate: currentDate,
dayDuration: weekDis
});
let newDateStr = dateFormat(newDate, "yyyy-MM-dd");
if (time) newDateStr = `${newDateStr} ${time}`;
return stringToDate(newDateStr);
}
function splitTimeZero(time) {
let timeList = time.split(":");
return `${timeList[0]}:${timeList[1]}`
}
function getTimeDesc(targetTime) {
if (!targetTime) return "";
let current = new Date();
let target = stringToDate(targetTime);
let distance = current.getTime() - target.getTime();
if (distance < (1000 * 60)) {
return `${parseInt(distance / 1000)}秒`;
} else if (distance < (1000 * 60 * 60)) {
return `${parseInt(distance / (1000 * 60))}分钟`
} else if (distance < (1000 * 60 * 60 * 24)) {
return `${parseInt(distance / (1000 * 60 * 60))}小时`
} else {
return `${parseInt(distance / (1000 * 60 * 60 * 24))}天`
}
}
module.exports = {
MIN_DAY_OF_WEEK,
MAX_DAY_OF_WEEK,
processDayOfWeek,
dateFormat,
getDate,
stringToDate,
getDateModel,
getAssignWeekTime,
getDayDis,
getHourDis,
getMinDis,
splitTimeZero,
getTimeDesc
}
\ No newline at end of file
const ENUM_SHOW_TYPE = {
LOADING: "loading",
MESSAGE: "none",
SUCCESS: "success",
FAIL: "error"
}
/**
* toast 展示
*/
function toastShow({
type = ENUM_SHOW_TYPE.LOADING, // enum_show_type
msg, // 文本
image, // 图片 不为空的情况下,除loading外其他type统一为message
mask = true, // 是否显示mask 拦截背景点击事件
duration = 2500, // 显示时间,loading不响应
callback, // show 回调 (success, data)
complete, // show complete 回调
}) {
let showParam = {
title: msg,
mask,
success: (res) => {
if (typeof callback == 'function') callback(true, res);
},
fail: (error) => {
if (typeof callback == 'function') callback(false, error);
},
complete: (res) => {
if (typeof complete == 'function') complete(res);
}
}
if (type == ENUM_SHOW_TYPE.LOADING) {
uni.showLoading({
...showParam
})
} else {
uni.showToast({
...showParam,
icon: type,
image,
duration,
})
}
}
function toastHide(param = {}) {
const {
type = ENUM_SHOW_TYPE.LOADING,
callback,
complete,
conflict = true
} = param;
let hideParam = {
noConflict: conflict,
success: (res) => {
if (typeof callback == 'function') callback(true, res);
},
fail: (error) => {
if (typeof callback == 'function') callback(false, error);
},
complete: (res) => {
if (typeof complete == 'function') complete(res);
}
}
if (type == ENUM_SHOW_TYPE.LOADING) {
uni.hideLoading(hideParam)
} else {
uni.hideToast(hideParam);
}
}
function toastLoading(msg = '加载中') {
toastShow({
type: ENUM_SHOW_TYPE.LOADING,
msg
})
}
function toastSuccess(msg = '成功') {
toastShow({
type: ENUM_SHOW_TYPE.SUCCESS,
msg
})
}
function toastWarn(msg = '错误') {
toastShow({
type: ENUM_SHOW_TYPE.FAIL,
msg
})
}
function toastMessage(msg) {
toastShow({
type: ENUM_SHOW_TYPE.MESSAGE,
msg
})
}
module.exports = {
ENUM_SHOW_TYPE, // LOADING SUCCESS, FAIL, MESSAGE
toastShow, // toast 展示 loading 和 toast 同时只存在一个
toastHide, // toast 隐藏
toastLoading, // loading 显示(最大文本数量7个汉字)
toastSuccess, // success 提示(最大文本数量7个汉字)
toastWarn, // warn 提示 (最大文本数量7个汉字)
toastMessage, // message 显示 (最大文本数量2行)
}
\ No newline at end of file
const {
printError
} = require("./printUtil");
/**
* 解析请求路径参数对象
* @param {string} url 请求路径
*/
function getObjectFromUrl(url) {
let tempObj = {};
if (/\?/.test(url)) {
let paramStr = url.substring(url.indexOf("?") + 1);
let paramArray = paramStr.split("&");
paramArray.forEach(item => {
let resultArray = item.split("=");
tempObj[resultArray[0]] = resultArray[1];
});
}
return tempObj;
}
/**
* 获取路径 参数字串
* @param {object} params 参数对象
*/
function appendParam(params, appendMark = true) {
if (typeof params == 'object') {
let keys = Object.keys(params);
if (keys && keys.length > 0) {
let list = keys.map(item => {
return `${item}=${params[item]}`;
})
return `${appendMark?'?':''}${list.join("&")}`
}
return "";
} else {
throw new Error("Params 类型错误")
}
}
/** 过滤空值属性 */
function emptyPrototypeFilter(obj) {
let result = {};
Object.keys(obj).forEach(item => {
if (obj[item] != null && obj[item] != undefined) {
result[item] = obj[item]
}
})
return result;
}
/**
* 拨打电话
* @param {string} phoneNumber 电话号码
*/
function callPhoneNumber(phoneNumber) {
if (!phoneNumber) return;
uni.makePhoneCall({
phoneNumber,
}).catch((error) => {
printError(error.errMsg);
})
}
/** 检查用户授权 */
function checkAuthSetting(settingName, callback) {
uni.getSetting({
success: (getSettingRes) => {
if (getSettingRes.authSetting[`scope.${settingName}`] == undefined) { // 未接受或拒绝过此权限
uni.authorize({
scope: `scope.${settingName}`,
success: (authorizeRes) => { // 同意授权
if (typeof callback == 'function') callback(true);
},
fail: (authorizeError) => {
if (typeof callback == 'function') callback(false,
`未授权: ${settingName}`)
}
})
} else if (getSettingRes.authSetting[`scope.${settingName}`] == false) { // 拒绝权限
openSettingView(settingName, callback);
} else { // 已授权
if (typeof callback == 'function') callback(true);
}
}
})
}
/** 打开授权页面 */
function openSettingView(settingName, callback) {
uni.openSetting({
success: (res) => {
if (res.authSetting[`scope.${settingName}`]) { // 同意授权
if (typeof callback == 'function') callback(true)
} else {
if (typeof callback == 'function') callback(false, `未授权:${settingName}`)
}
},
fail: (error) => {
if (typeof callback == 'function') callback(false, 'openSetting 失败')
}
})
}
/**
* 电话隐私处理
*/
function privatePhone(phone, start = 3, end = 7, replace = '****') {
if (!phone || phone.length <= end) return phone;
return phone.replace(phone.substring(start, end), replace)
}
/**
* 姓名隐私处理
* @param {Object} name
*/
function privateName(name) {
if (!name) return name;
name = name.replace(" ", "");
let result = "";
name = name.split("");
name.forEach((item) => {
if (!result) {
result = item;
} else {
result = `${result}*`
}
})
return result;
}
/**
* px rpx 转换
*/
const pxrpxChangeMap = {};
function rpx2px(rpx) {
let key = `rpx${rpx}`;
if (pxrpxChangeMap[key]) return pxrpxChangeMap[key];
let result = 0;
let deviceWidth = uni.getSystemInfoSync().windowWidth; //获取设备屏幕宽度
let px = (deviceWidth / 750) * Number(rpx)
result = Math.floor(px);
return result;
}
function px2rpx(px) {
let key = `px{px}`;
if (pxrpxChangeMap[key]) return pxrpxChangeMap[key];
let result = 0;
let deviceWidth = uni.getSystemInfoSync().windowWidth; //获取设备屏幕宽度
let rpx = (750 / deviceWidth) * Number(px)
result = Math.floor(rpx);
return result;
}
/**
* 是否是array
* @param {Object} list
* @returns true == array | false != array
*/
function isArray(list) {
return Object.prototype.toString.call(list) == '[object Array]';
}
/**
* 是否是空
* @param {Object} data 要检查的数据
* @param {Boolean} onlyNull 是否只检查是否为 null 或者 undefined
* @returns true == empty | false != empty
*/
function isEmpty(data, onlyNull = false) {
if (onlyNull) {
return data === null || data === undefined;
} else {
if (typeof data == 'object') {
if (data) {
let result = JSON.stringify(data);
return result == "{}" || result == "[]"
} else {
return true;
}
} else {
return data ? false : true;
}
}
}
/**
* 是否不为空
*/
function isNotEmpty(data, onlyNull = false) {
return !isEmpty(data, onlyNull);
}
/**
* 获取随机数
* @param {Object} min
* @param {Object} max
* @param {Boolean} include
*/
function getRandomInt(min = 0, max = 1, include = true) {
min = Math.ceil(min);
max = Math.floor(max);
return include ? Math.floor(Math.random() * (max - min + 1)) + min : Math.floor(Math.random() * (max - min)) + min;
}
/**
* 版本比对
* @param {Object} versionA
* @param {Object} versionB
* @returns 1 versionA 大于 versionB || -1 versionA 小于 versionB || 0 两个version相等
*/
function compareVersion(versionA, versionB) {
const versionArr_A = versionA.split(".");
const versionArr_B = versionB.split(".");
const maxLength = Math.max(versionArr_A.length, versionArr_B.length);
for (let i = 0; i < maxLength; i++) {
const a = versionArr_A[i] || 0;
const b = versionArr_B[i] || 0;
if (Number(a) > Number(b)) {
return 1;
} else if (Number(a) < Number(b)) {
return -1;
}
if (i === maxLength - 1) {
return 0;
}
}
}
/**
* 遍历枚举
*/
function ergodicEnum(enumObj, value, compareKey = 'value') {
let result = null;
if (value == null || value == undefined) return result;
if (typeof enumObj == 'object') {
if (enumObj instanceof Array) {
enumObj.map((item, index) => {
return {
...item,
index
}
}).forEach((item) => {
if (value == item[compareKey]) {
result = item;
}
})
} else {
Object.keys(enumObj).forEach((keyItem, keyIndex) => {
let item = {
...enumObj[keyItem],
index: keyIndex
};
if (value == item[compareKey]) {
result = item;
}
})
}
}
return result;
}
/** 通过 Value 获取 Name */
function getEnumValueName(enumObj, value) {
let result = ergodicEnum(enumObj, value);
if (result) {
return result.name;
} else {
return "";
}
}
/** 通过 Value 获取 Index */
function getEnumValueIndex(enumObj, value) {
let result = ergodicEnum(enumObj, value);
if (result) {
return result.index;
} else {
return -1;
}
}
/** 通过 Name 获取 Value */
function getEnumNameValue(enumObj, name) {
let result = ergodicEnum(enumObj, name, 'name');
if (result) {
return result.value;
} else {
return '';
}
}
/** 通过 Name 获取 Index */
function getEnumNameIndex(enumObj, name) {
let result = ergodicEnum(enumObj, name, 'name');
if (result) {
return result.index;
} else {
return -1;
}
}
/** 通过 Index 获取 Value */
function getEnumIndexValue(enumObj, index) {
let result = ergodicEnum(enumObj, index, 'index');
if (result) {
return result.value
} else {
return ""
}
}
/** 通过 Index 获取 Name */
function getEnumIndexName(enumObj, index) {
let result = ergodicEnum(enumObj, index, 'index');
if (result) {
return result.name
} else {
return ""
}
}
//生成从minNum到maxNum的随机数
function randomNum(minNum, maxNum) {
switch (arguments.length) {
case 1:
return parseInt(Math.random() * minNum + 1, 10);
case 2:
return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
default:
return 0;
}
}
module.exports = {
getObjectFromUrl,
appendParam,
emptyPrototypeFilter,
callPhoneNumber,
checkAuthSetting,
privatePhone,
privateName,
rpx2px,
px2rpx,
isArray,
isEmpty,
isNotEmpty,
getRandomInt,
compareVersion,
ergodicEnum,
getEnumValueName,
getEnumValueIndex,
getEnumNameValue,
getEnumNameIndex,
getEnumIndexValue,
getEnumIndexName,
randomNum
}
\ No newline at end of file
const {
isEmpty
} = require("./util");
const {
ENUM_VALIDATOR_RULE,
ENUM_VALIDATOR_TYPE
} = require("./enum_utils.js");
/** 校验策略 */
const validatorStrategies = {
[`${ENUM_VALIDATOR_RULE.IS_NOT_EMPTY}`]: isNotEmpty,
[`${ENUM_VALIDATOR_RULE.IS_MOBILE}`]: isMobile,
[`${ENUM_VALIDATOR_RULE.IS_LAND_LINE}`]: isLandLine,
[`${ENUM_VALIDATOR_RULE.IS_ID_CARD}`]: isIDCard,
[`${ENUM_VALIDATOR_RULE.MIN_LENGTH}`]: minLength,
[`${ENUM_VALIDATOR_RULE.MAX_LENGTH}`]: maxLength,
[`${ENUM_VALIDATOR_RULE.IS_SAFE_PASSWORD}`]: isSafePassword,
[`${ENUM_VALIDATOR_RULE.IS_NUMBER}`]: isNumber,
[`${ENUM_VALIDATOR_RULE.IS_STRING}`]: isString,
[`${ENUM_VALIDATOR_RULE.IS_ENCHAR}`]: isENChar,
[`${ENUM_VALIDATOR_RULE.IS_NULL}`]: isNull,
}
// 校验是否为空
function isNotEmpty(checkData, errorMsg = '不能为空') {
if (isEmpty(checkData)) return errorMsg;
}
// 校验是否为 null
function isNull(checkData, errorMsg = '数据为空') {
if (isEmpty(checkData, true)) return errorMsg;
}
/** 校验是否是string */
function isString(checkData, errorMsg = '非字符串') {
if (typeof checkData == 'string') return errorMsg;
}
/** 校验是否是数字 */
function isNumber(checkData, errorMsg = '非数字') {
const regex = /^[0-9]+.?[0-9]*$/;
if (!regex.test(checkData)) return errorMsg
}
/** 校验是否是英文字符 */
function isENChar(checkData, errorMsg = '非英文字符') {
const regex = /^[A-Za-z]+$/;
if (!regex.test(checkData)) return errorMsg;
}
/** 校验密码安全 */ // 字符开头 字符+数字+下划线 6 - 18位
function isSafePassword(checkData, errorMsg = '密码不安全') {
// /^\w+$/ 数字、字符、下划线
const regex = /^[a-zA-Z]\w{5,17}$/
if (!regex.test(checkData)) return errorMsg;
}
/** 校验数据最小长度 */
function minLength(checkData, length = 0, errorMsg = '超出最小长度') {
if (checkData.length < length) return errorMsg;
}
/** 校验数据最大长度 */
function maxLength(checkData, length, errorMsg = '超出最大长度') {
if (checkData.length > length) return errorMsg;
}
// 校验座机
function isLandLine(checkData, errorMsg = '错误的座机号码') {
const regex = /^(\d{3,4}-)?\d{7,8}$/
if (!regex.test(checkData)) return errorMsg;
}
// 校验手机
function isMobile(checkData, errorMsg = '错误的手机号码') {
const regex = /^1[3|4|5|6|7|8|9][0-9]{9}$/;
if (!regex.test(checkData)) return errorMsg;
}
// 校验身份证
function isIDCard(checkData, errorMsg = '错误的身份证号码') {
const regex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
if (!regex.test(checkData)) return errorMsg;
}
/** =====================校验对象===================== */
let Validator = function() {
this.cache = []; // 校验规则缓存
this.validatorType = ENUM_VALIDATOR_TYPE.OR; // 整体校验规则: or 或(只有一个失败就抛出)| and 且(全部错误就抛出)
}
/**
* 校验对象添加校验规则
* @param {any} checkData 要检查的数据
* @param {string} rule 校验规则---校验策略名 或者 校验策略名:校验限制参数(以:进行分割)
* @param {string} errMsg 校验错误信息
* @param {object} context 校验上下文,默认为null
*/
Validator.prototype.add = function(checkData, rule, errMsg, context = null) {
let array = rule.split(":");
this.cache.push(function() { // 使用空函数包装校验方法,并放入缓存
var strategyKey = array.shift(); // 获取校验规则策略名称
array.unshift(checkData); // 在首位插入要校验的数据
if (errMsg) {
array.push(errMsg) // 在末尾插入错误信息
}
return validatorStrategies[strategyKey].apply(context, array)
})
}
/**
* 触发校验
*/
Validator.prototype.start = function() {
// 循环检验规则缓存
let errMsgList = [];
for (let i = 0; i < this.cache.length; i++) {
let errMsg = this.cache[i](); // 开始执行缓存中的校验方法
if (errMsg) { // 如果有返回值,校验没有通过, 抛出错误信息
errMsgList.push(errMsg);
if (this.validatorType == ENUM_VALIDATOR_TYPE.OR) return errMsg;
}
}
if (this.validatorType == ENUM_VALIDATOR_TYPE.AND) {
if (errMsgList && errMsgList.length == this.cache.length) { // 错误信息缓存长度 不等于 校验缓存长度, 有通过校验的内容
return errMsgList[0];
} else {
return null;
}
}
return null;
}
/**
* 设置整体校验规则
* @param {string} type // or 或(只有一个失败就抛出)and 且(全部错误就抛出)
*/
Validator.prototype.setValidatorType = function(type) {
if (type != ENUM_VALIDATOR_TYPE.OR && type != ENUM_VALIDATOR_TYPE.AND) {
type = ENUM_VALIDATOR_TYPE.OR
}
this.validatorType = type;
}
export default Validator;
\ No newline at end of file
<template>
<view class="book-list-item" @click="tapItem">
<view class="cover-box item">
<!-- <image v-show="imageError" class="cover" src="/static/images/image_error.png" mode="aspectFill"></image> -->
<image class="cover" :src="item.avatar" mode="aspectFill"></image>
</view>
<view class="c-flex_column">
<view class="c-flex_row c-justify_between row">
<view class="c-flex_column c-justify_center item">
<view class="title">
{{item.title}}
</view>
</view>
<view class="close-button item" v-if='showClose'>
<uni-icons type="closeempty" size='12' color="#999"></uni-icons>
<view class="cover" @click.stop="tapClose"></view>
</view>
</view>
<view class="c-flex_row row">
<view class="c-flex_column c-justify_between content c-flex_1 item">
<view class="desc row">
{{item.summary}}
</view>
<view class="c-flex_row c-align_center label-box row">
<slot name="footer">
<view class="label label-color-1" v-if='showCategory'>
{{item.categoryName}}
</view>
<view class="label label-color-2" v-for='(label, labelIndex) in item.tagList'
:key='labelIndex'>
{{label.name}}
</view>
</slot>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
item: {
type: Object,
default: function() {
return {}
}
},
showClose: {
type: Boolean,
default: true
}
},
data: function() {
return {
imageError: true
}
},
watch: {},
computed: {
showCategory: function() {
return this.item.categoryName
},
showErrorImage: function() {
return this.imageError;
},
hideErrorImage: function() {
return !this.imageError
}
},
methods: {
tapItem() {
this.$emit("tapItem", {
detail: {
data: this.item
}
})
},
tapClose() {
this.$emit("close", {
detail: {
data: this.item
}
})
},
loadImage() {
this.imageError = false;
},
errorImage() {
this.imageError = true
}
}
}
</script>
<style lang="scss" scoped>
.book-list-item {
display: flex;
flex-direction: row;
padding: 20rpx 40rpx;
.row {
margin-bottom: 20rpx;
}
.row:last-child {
margin-bottom: 0;
}
.item {
margin-right: 20rpx;
}
.item:last-child {
margin-right: 0;
}
.title {
font-size: 32rpx;
font-weight: 700;
color: #333;
}
.close-button {
border-radius: 10rpx;
border: 2rpx solid #999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 40rpx;
height: 30rpx;
position: relative;
.cover {
position: absolute;
top: 50%;
left: 50%;
width: 70rpx;
height: 70rpx;
transform: translate(-50%, -50%);
z-index: 2;
background: transparent;
}
}
.desc {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
font-size: 26rpx;
color: #999
}
.label-box {
flex-wrap: wrap;
.label {
padding: 5rpx 10rpx;
font-size: 20rpx;
margin-right: 20rpx;
border-radius: 10rpx;
}
.label-color-1 {
background: #faefe6;
color: #cc6008;
}
.label-color-2 {
color: #3d99fd;
background: #d8ebff;
}
.label-color-3 {
background: #ff8787;
color: #ff3737;
}
}
}
.cover-box {
.cover {
width: 150rpx;
height: 200rpx;
border-radius: 15rpx;
}
}
</style>
\ No newline at end of file
<template>
<view class="search-box" @click="search">
<view class="search-zone c-flex_row c-align_center">
<view class="item">
<uni-icons type='search' size='28' color="#e5e5e5"></uni-icons>
</view>
<input class="item c-flex_1" placeholder="搜索书名/作者名" disabled />
<view class="button item">
搜索
</view>
</view>
</view>
</template>
<script>
import {
gotoBookSearchPage
} from "../../common/services/page-route.js"
import {
ENUM_SEARCH_TYPE
} from "../../static/enums/enum_value.js";
export default {
props: {
searchType: {
type: Object,
default: function() {
return ENUM_SEARCH_TYPE.WAREHOUSE
}
}
},
methods: {
search() {
gotoBookSearchPage(this.searchType);
}
}
}
</script>
<style lang="scss" scoped>
.search-box {
background: transparent;
padding: 10rpx 20rpx;
.search-zone {
border-radius: 35rpx;
background: #fff;
padding: 4rpx;
font-size: 28rpx;
.item {
margin-left: 20rpx;
}
.button {
height: 70rpx;
line-height: 70rpx;
width: 120rpx;
text-align: center;
color: #fff;
background: #007aff;
border-radius: 35rpx;
}
}
}
</style>
\ No newline at end of file
<template>
<view>
<c-navi id='navi'></c-navi>
<book-search-box id='search' :searchType='searchType'></book-search-box>
<read-time-count-row id='count' ref='timeCount' @resize='resizeTimeCount'></read-time-count-row>
<BookshelfList ref='bookList' :height='listHeight'></BookshelfList>
<c-login></c-login>
</view>
</template>
<script>
import BookshelfList from "./components/bookshelf-list.vue";
import SystemInfoMixin from "../../common/mixins/system-info-mixin.js"
import {
watchUserInfoChange,
removeUserInfoChangeWatch
} from "../../common/services/userServices.js";
import {
ENUM_SEARCH_TYPE
} from "../../static/enums/enum_value";
import {
removeCollectionChangeWatch,
watchCollectionChange
} from "../../common/services";
export default {
mixins: [SystemInfoMixin],
components: {
BookshelfList
},
data() {
return {
searchType: ENUM_SEARCH_TYPE.BOOKSHELF,
listHeight: 0
};
},
onReady() {
this.initHeight();
// 监听用户变动
watchUserInfoChange((info) => {
if (info.userInfo) {
this.initData();
}
}, this)
// 监听收藏变动
watchCollectionChange(() => {
this.refreshList();
}, this);
},
onShow() {
// 更新阅读时间统计
this.refreshTimeCount();
},
onUnload() {
// 移除监听
removeUserInfoChangeWatch(this);
removeCollectionChangeWatch(this);
},
methods: {
resizeTimeCount() {
this.initHeight();
},
initData() {
this.refreshList();
},
// 初始化刷新
initRefresh() {
let ref = this.$refs.bookList;
if (ref) {
ref.initRefresh();
}
},
// 刷新数据列表
refreshList() {
let ref = this.$refs.bookList;
if (ref) {
ref.refreshList();
}
},
refreshTimeCount() {
let ref = this.$refs.timeCount;
if (ref) {
ref.refresh();
}
},
initHeight() {
const query = uni.createSelectorQuery().in(this);
query.select("#navi").boundingClientRect();
query.select("#search").boundingClientRect();
query.select("#count").boundingClientRect();
query.exec((res) => {
let result = 0;
res.forEach(item => {
result = result + item.height;
})
this.listHeight = this.windowHeight - result;
})
},
}
}
</script>
<style lang="scss">
</style>
\ No newline at end of file
<template>
<view>
<c-list ref='list' :showShelfEmpty="true" flag='bookshelf' :needLogin="true" :height="height" url='/v1/collect/'
:param="requestParam" @change='changeData'>
<book-list-item v-for='(item, index) in dataList' :key='index' :item='item'
@tapItem='tapItem($event, index)' @close='tapClose($event, index)'>
<template v-slot:footer>
<view class="c-flex_row c-aligns_center">
<uni-icons type='calendar' size="16" color="#999"></uni-icons>
<view class="info">
{{item.lastReadTimeDesc}} 前阅读过
</view>
</view>
</template>
</book-list-item>
</c-list>
</view>
</template>
<script>
import {
gotoBookContentPage
} from '../../../common/services/page-route';
import {
isEmpty
} from '../../../common/utils/util';
import BookshelfBookItem from '../models/BookshelfBookItem';
import {
collectionBook,
noticeCollectionListChange
} from "../../../common/services/index.js"
export default {
props: {
height: {
type: Number,
default: 0
},
},
data: function() {
return {
dataList: [],
}
},
computed: {
requestParam: function() {
return {}
}
},
methods: {
initRefresh() {
if (isEmpty(this.dataList)) {
this.refreshList();
}
},
refreshList() {
let ref = this.$refs.list;
if (ref) {
ref.onPullRefreshing();
}
},
changeData(e) {
this.dataList = e.detail.data.map(item => {
return new BookshelfBookItem(item)
})
},
tapItem(e, index) {
gotoBookContentPage(e.detail.data.id)
},
tapClose(e, index) {
let item = e.detail.data;
uni.showModal({
title: "确认移除",
content: `是否确认从书架中移除《${item.title}》`,
success: (res) => {
if (res.confirm) {
collectionBook(false, item.id, (success, data) => {
if (success) {
this.dataList.splice(index, 1);
this.$forceUpdate();
noticeCollectionListChange(item.id, false);
}
})
}
}
})
}
}
}
</script>
<style lang="scss" scoped>
.info {
font-size: 26rpx;
color: #999;
margin-left: 10rpx;
}
</style>
\ No newline at end of file
import Book from "../../../common/models/Book";
import {
getTimeDesc
} from "../../../common/utils/timeUtil";
export default class BookshelfBookItem extends Book {
constructor(param) {
super(param || {})
const {
lastReadTime
} = param || {}
this.lastReadTime = lastReadTime || "";
this.lastReadTimeDesc = lastReadTime ? getTimeDesc(lastReadTime) : "";
}
}
\ No newline at end of file
module.exports = {}
\ No newline at end of file
<template>
<view v-if="false" class="c-empty" :style="[emptyStyle]">
<image class='empty-image' :style="[emptyImgStyle]" :src='emptyImg'>
</image>
<view class="empty-title" :style="[emptyTitleStyle]">
{{emptyTitle}}
</view>
<slot></slot>
</view>
</template>
<script>
export default {
name: "c-empty",
props: {
type: {
type: String,
default: "base", // sm | base | lg
},
direction: {
type: String,
default: "column", // column 列形式, row 行形式
},
emptyTitle: {
type: String,
default: "数据为空~"
},
emptyImg: {
type: String,
default: "/static/images/c-empty/empty.png"
}
},
data() {
return {
};
},
computed: {
emptyTitleStyle: function() {
let result;
switch (this.type) {
case "sm":
result = {
"font-size": "22rpx"
};
break;
case "base":
result = {
"font-size": "28rpx"
};
break;
case "lg":
result = {
"font-size": "32rpx"
};
break;
default:
result = {
"font-size": "28rpx"
};
break;
}
switch (this.direction) {
case 'row':
result = {
...result,
"margin-bottom": "0"
}
break;
}
return result;
},
emptyImgStyle: function() {
let result;
switch (this.type) {
case "sm":
result = {
width: "60rpx",
height: "60rpx"
};
break;
case "base":
result = {
width: "150rpx",
height: "150rpx"
};
break;
case "lg":
result = {
width: "240rpx",
height: "240rpx"
};
break;
default:
result = {
width: "150rpx",
height: "150rpx"
};
break;
}
return result;
},
emptyStyle: function() {
let result;
switch (this.type) {
case "sm":
result = {
height: "150rpx"
}
break;
case "base":
result = {
height: "350rpx"
}
break;
case "lg":
result = {
height: "700rpx"
}
break;
default:
result = {
height: "350rpx"
}
break;
}
switch (this.direction) {
case "column":
result = {
...result,
"flex-direction": "column"
}
break;
case "row":
result = {
...result,
"flex-direction": "row"
}
break;
default:
result = {
...result,
"flex-direction": "column"
}
break;
}
return result;
}
}
}
</script>
<style lang="scss" scoped>
.c-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
.empty-image {}
.empty-title {
color: #999;
margin-bottom: 30rpx;
}
}
</style>
\ No newline at end of file
<template>
<scroll-view class="c-list" :style="[scrollStyle]" scroll-y :refresher-enabled='ableRefresh'
refresher-threshold='100' :enable-flex='true' :refresher-triggered='refreshTrigger' @refresherrefresh='refresh'
@scrolltolower='onLoadMore'>
<view class="content" :style="[contentStyle]">
<template v-if='showCover'>
<slot name="cover"></slot>
</template>
<template v-else-if='showEmpty'>
<c-shelf-empty v-if="showShelfEmpty"></c-shelf-empty>
<c-empty v-else></c-empty>
</template>
<template v-else>
<uni-list>
<slot></slot>
</uni-list>
</template>
</view>
<uni-load-more v-if='ableLoadMore' :status="loadMoreStatus.value" :contentText="loadMoreContentText"
iconType="snow"></uni-load-more>
</scroll-view>
</template>
<script>
import {
isEmpty
} from '../../common/utils/util.js'
import {
LOAD_MORE_STATUS
} from '../../static/enums/enum_value';
import {
apiRequest
} from "../../common/utils/apiRequest.js"
import {
watchUserInfoChange,
removeUserInfoChangeWatch,
} from "../../common/services/userServices.js"
const DEFAULT_PAGE_NO = 1;
export default {
name: "c-list",
props: {
flag: {
type: String,
default: ""
},
height: {
type: Number,
default: 0
},
bgColor: {
type: String,
default: "#fff"
},
/** 控制标识 */
ableRefresh: {
type: Boolean,
default: true,
},
ableLoadMore: {
type: Boolean,
default: true
},
showCover: {
type: Boolean,
default: false
},
showShelfEmpty: {
type: Boolean,
default: false
},
/** 初始数据源 */
sources: {
type: Array,
default: function() {
return []
}
},
total: {
type: Number,
default: 0
},
/** 请求 */
url: {
type: String,
default: ""
},
param: {
type: Object,
default: function() {
return {}
}
},
method: {
type: String,
default: "GET"
},
header: {
type: Object,
default: function() {
return {}
}
},
customRequest: {
type: Boolean,
default: false
}, // 是否使用自定义请求,cList 只对外提供下拉和上拉动作,不做数据处理,列表状态变动需主动调用组件接口处理
needLogin: {
type: Boolean,
default: false
}, // 是否需要登录
},
data() {
return {
userInfo: null,
pageNo: DEFAULT_PAGE_NO,
pageSize: 6,
totalCount: 0,
dataList: [],
loading: false,
/** loadmore */
loadMoreStatus: LOAD_MORE_STATUS.NO_MORE,
loadMoreContentText: {},
/** refreshing */
refreshTrigger: false,
};
},
computed: {
showEmpty: function() {
return isEmpty(this.dataList);
},
scrollStyle: function() {
return {
height: `${this.height||uni.getSystemInfoSync().windowHeight}px`,
background: `${this.bgColor||"#fff"}`
}
},
contentStyle: function() {
return {
minHeight: `${this.height||uni.getSystemInfoSync().windowHeight}px`
}
},
loadMoreStatusChange: function() {
const {
totalCount,
dataList
} = this;
return {
totalCount,
dataList
}
}
},
watch: {
loadMoreStatusChange: {
handler: function(n) {
if (n.dataList && !this.customRequest) {
this.changeLoadMoreStatus(n.totalCount > n.dataList.length, false);
if (n.totalCount > n.dataList.length) {
this.pageNoStep();
}
}
},
immediate: true,
deep: true
},
total: {
handler: function(n) {
this.totalCount = n
},
immediate: true
},
sources: {
handler: function(n) {
this.dataList = n
},
immediate: true
}
},
mounted() {
this.getLoadMoreContentText();
watchUserInfoChange((info) => {
this.userInfo = info.userInfo;
}, this)
},
destroyed() {
removeUserInfoChangeWatch(this)
},
methods: {
/** customRequest 需要使用的接口 */
changeLoadMoreStatus(isMore, isLoading) {
if (isLoading) {
this.loadMoreStatus = LOAD_MORE_STATUS.LOADING
} else if (isMore) {
this.loadMoreStatus = LOAD_MORE_STATUS.MORE
} else {
this.loadMoreStatus = LOAD_MORE_STATUS.NO_MORE
}
},
pageNoStep() {
this.pageNo++;
},
openTrigger() {
if (this.refreshTrigger) {
this.refreshTrigger = false;
}
this.refreshTrigger = true;
},
closeTrigger() {
if (!this.refreshTrigger) {
this.refreshTrigger = true;
}
setTimeout(() => {
this.refreshTrigger = false;
}, 300)
},
getLoadMoreContentText() {
this.loadMoreContentText = {
contentdown: LOAD_MORE_STATUS.MORE.name,
contentrefresh: LOAD_MORE_STATUS.LOADING.name,
contentnomore: LOAD_MORE_STATUS.NO_MORE.name
}
},
initRefresh() {
if (this.customRequest) {
this.onPullRefreshing();
} else {
if (isEmpty(this.dataList)) {
this.onPullRefreshing();
}
}
},
/** 外部触发 变更trigger list 响应 refresh 接口 */
onPullRefreshing() {
if (this.loading && !this.customRequest) {
this.closeTrigger();
return;
}
this.openTrigger();
},
/** 响应触发 响应 list 回调 */
refresh() {
if (this.loading && !this.customRequest) { // loading 用于内部数据判断标识, 所以要判断是否启用 customeRequest
this.closeTrigger();
return;
}
this.pageNo = DEFAULT_PAGE_NO;
if (this.customRequest) {
this.$emit("refresh", {
detail: {
pageNo: this.pageNo,
pageSize: this.pageSize,
}
})
} else {
this.requestData();
}
},
onLoadMore() {
if (!this.ableLoadMore) return;
if (this.totalCount <= this.dataList.length && !this.customRequest)
return; // totalCount 和 datalist 用于内部数据判断标识,所以要判断是否启用 customeRequest
if (this.loading && !this.customRequest) return; // loading 用于内部数据判断标识, 通上
this.closeTrigger();
this.changeLoadMoreStatus(false, true);
if (this.customRequest) {
this.$emit("loadMore", {
detail: {
pageNo: this.pageNo,
pageSize: this.pageSize
}
})
} else {
this.requestData();
}
},
/** customeRequst 不会使用的接口*/
requestData() {
if (this.needLogin && !this.userInfo) {
this.loading = false;
this.closeTrigger();
return;
}
this.loading = true;
let param = {
pageNo: this.pageNo,
pageSize: this.pageSize
}
if (this.param) {
param = {
...param,
...this.param
}
}
apiRequest({
url: this.url,
method: this.method || "GET",
data: param,
header: this.header || {},
callback: (success, data) => {
this.loading = false;
this.closeTrigger();
if (success) {
let rows = data.records;
let total = data.total;
if (!rows) {
rows = data;
total = data.length;
}
this.totalCount = total;
if (this.pageNo == DEFAULT_PAGE_NO) {
this.dataList = rows;
} else {
this.dataList = this.dataList.concat(rows);
}
}
this.$emit("change", {
detail: {
data: this.dataList,
total: this.totalCount,
}
})
}
})
}
}
}
</script>
<style lang="scss" scoped>
.c-list {
height: 600px;
}
</style>
\ No newline at end of file
<template>
<uni-popup ref="pop" type="center" :is-mask-click='false' :safe-area='false' @maskClick='tapMask'>
<view class="login-content">
<view class="close-button" @click.stop="tapMask">
<view class="icon-box">
<uni-icons type='closeempty' size="24" color="#cc6008"></uni-icons>
</view>
</view>
<view class="welcome-msg">
快速登录
</view>
<view class="login-button">
<image class="logo" src="/static/images/logo.png" mode="scaleToFill"></image>
<view class="info">
授权登录
</view>
<button class="c-button_clear cover-button" @click="getPhoneNumber"></button>
</view>
</view>
</uni-popup>
</template>
<script>
import SystemInfoMixin from "../../common/mixins/system-info-mixin.js";
import {
addNormalNotificationObserver,
postNotification,
removeNotificationObserver
} from "../../common/utils/notificationCenter.js";
import {
px2rpx
} from "../../common/utils/util.js";
import {
KEY_NOTIFICATION_LOGIN_SHOW,
KEY_NOTIFICATION_LOGIN_SUCCESS
} from "../../static/keys/notification-keys.js"
import {
login,
logout
} from "../../common/services/userServices.js"
export default {
mixins: [SystemInfoMixin],
name: "c-login",
props: {
show: {
type: Boolean,
default: false
},
isShareLink: {
type: Boolean,
default: false
}
},
data() {
return {
showPop: false,
};
},
computed: {
safePlacehoderStyle: function() {
let height = 0;
if (this.bottomSafeHeight) {
height = height + px2rpx(this.bottomSafeHeight)
}
return {
height: `${height}rpx`
}
},
},
watch: {
show: function(n) {
this.showPop = n;
},
showPop: function(n, o) {
if (n == o) return;
if (n) {
this.open();
} else {
this.close();
}
}
},
mounted() {
addNormalNotificationObserver(KEY_NOTIFICATION_LOGIN_SHOW, (info) => {
if (this.showPop != info.show) {
this.showPop = info.show
}
}, this);
addNormalNotificationObserver(KEY_NOTIFICATION_LOGIN_SUCCESS, () => {
this.showPop = false;
}, this)
},
destroyed() {
removeNotificationObserver(KEY_NOTIFICATION_LOGIN_SUCCESS, this);
removeNotificationObserver(KEY_NOTIFICATION_LOGIN_SHOW, this);
},
methods: {
open() {
this.$refs.pop.open();
},
close() {
this.$refs.pop.close();
postNotification(KEY_NOTIFICATION_LOGIN_SHOW, {
show: false
})
},
tapMask() {
logout(() => {}, true);
this.showPop = false;
postNotification(KEY_NOTIFICATION_LOGIN_SHOW, {
show: false
})
},
getPhoneNumber() {
this.ttLoging();
},
ttLoging() {
var that = this;
var obj = tt.getLaunchOptionsSync()
var channel = 'douyin' // 渠道
var bookId = '';
var remark1 = ''; // 预留
var remark2 = ''; // 预留
var remark3 = ''; // 预留
var remark4 = ''; // 预留
var remark5 = ''; // 预留
var remark6 = ''; // 预留
var thirdParam = '';
var tips2 = '';
var album_id = obj.query.tt_album_id ?? '';
var episode_id = obj.query.tt_episode_id ?? '';
uni.setStorage({
key: 'tt_album_id',
data: album_id
});
uni.setStorage({
key: 'tt_episode_id',
data: episode_id
});
// 抖音feed页参数
thirdParam = JSON.stringify(obj.query);
bookId = obj.query.book_id ?? ''
tips2 = obj.query.tips2 ?? ''
remark1 = obj.query.remark1 ?? ''
remark2 = obj.query.remark2 ?? ''
remark3 = obj.query.remark3 ?? ''
remark4 = obj.query.remark4 ?? ''
remark5 = obj.query.remark5 ?? ''
remark6 = obj.query.remark6 ?? ''
if (this.tips1 == 'mints_book' || tips2 == 'mints_book') {
this.slotParam = JSON.stringify(obj.query);
this.$refs.select.open('center');
}
tt.login({
force: true,
success(res) {
login({
channel: channel,
clueToken: '',
anonymousCode: res.anonymousCode,
code: res.code,
adId: '',
advertiserId: '',
reqId: '',
remark1: remark1,
remark2: remark2,
remark3: remark3,
remark4: remark4,
remark5: remark5,
remark6: remark6,
thirdParam: thirdParam,
promotionId: '',
projectId: '',
bookId: bookId,
})
},
fail(res) {
// 登录授权失败
uni.navigateTo({
url: `/pages/warehouse/warehousetemp`,
})
},
});
}
}
}
</script>
<style lang="scss">
.login-content {
position: relative;
width: 500rpx;
height: 300rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
border-radius: 20rpx;
overflow: hidden;
.close-button {
position: absolute;
width: 220rpx;
height: 220rpx;
top: 0;
right: 0;
transform: translate(50%, -50%);
background: #faefe6;
z-index: 2;
border-radius: 50%;
.icon-box {
position: absolute;
left: 30%;
bottom: 30%;
transform: translate(-50%, 50%);
}
}
.welcome-msg {
font-size: 36rpx;
color: #333;
font-weight: 700;
}
.login-button {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 100rpx;
line-height: 100rpx;
margin-top: 30rpx;
width: 400rpx;
background: #09bb07;
color: #fff;
border-radius: 20rpx;
.logo {
width: 60rpx;
height: 60rpx;
border-radius: 30rpx;
border: 2rpx solid #fff;
}
.info {
height: 100rpx;
line-height: 100rpx;
font-size: 30rpx;
margin-left: 20rpx;
}
.cover-button {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
background: transparent;
}
}
}
</style>
\ No newline at end of file
<template>
<view v-if="false" class="c-flex_column c-navi" :style="[naviStyle]">
<view class="status-bar" :style="[statusBarStyle]">
</view>
<view class="c-flex_row c-align_center navi-bar" :style="[naviBarStyle]">
<image class="logo" src="/static/images/logo.png" mode="scaleToFill"></image>
<view class="title">
{{title}}
</view>
</view>
</view>
</template>
<script>
import SystemInfoMixin from "../../common/mixins/system-info-mixin.js";
export default {
mixins: [SystemInfoMixin],
name: "c-navi",
props: {
title: {
type: String,
default: "幻墨小说阁"
}
},
data() {
return {
};
},
computed: {
naviStyle: function() {
return {
height: `50px`
}
},
statusBarStyle: function() {
return {
// height: tt.getSystemInfoSync().statusBarHeight + 'px'
height: `10px`
}
},
naviBarStyle: function() {
return {
height: `${this.naviBarHeight}px`
}
}
}
}
</script>
<style lang="scss">
.c-navi {
background: transparent;
.status-bar {
background: transparent;
}
.navi-bar {
background: transparent;
.logo {
width: 70rpx;
height: 70rpx;
margin-left: 20rpx;
border-radius: 50%;
}
.title {
height: 70rpx;
line-height: 70rpx;
font-size: 32rpx;
font-weight: 700;
color: #333;
margin-left: 20rpx;
}
}
}
</style>
\ No newline at end of file
<template>
<view class="c-empty">
<view class="empty-title">
暂未添加到书架
</view>
<view class="empty-button" @click="goWareHouse">
去书库
</view>
<slot></slot>
</view>
</template>
<script>
export default {
name: "c-shelf-empty",
data() {
return {
};
},
methods: {
goWareHouse() {
uni.switchTab({
url: '/pages/warehouse/warehouse'
});
}
},
}
</script>
<style lang="scss" scoped>
.c-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 800rpx;
.empty-title {
color: #999;
font-size: 26rpx;
margin-bottom: 40rpx;
}
.empty-button {
font-size: 30rpx;
border: 1px solid #FECF02;
padding: 10rpx 40rpx;
border-radius: 30rpx;
color: #FECF02;
}
}
</style>
\ No newline at end of file
<template>
<view class="c-tab-item" :class="tabItemClass" :style="[tabItemStyle]" @click="tap">
<uni-icon class='icon' :type='item.icon' :color='textColor' size='28' v-if='item.icon' :class='iconClass'>
</uni-icon>
<view class="title">
{{item[nameKey]}}
<view class="count" v-if='getCount'>
{{getCount}}
</view>
</view>
<view v-if='showUnderLine' class="under-line" :style='{background: textColor}'></view>
</view>
</template>
<script>
export default {
props: {
item: {
type: Object,
default: function() {
return {}
}
}, // item
nameKey: {
type: String,
default: "title"
}, // 显示字段
isCurrent: {
type: Boolean,
default: false
}, // 是否选中
direction: {
type: String,
default: "row"
}, // 方向
color: {
type: String,
default: "#5eb670"
}, // 未选中文本颜色 | 选中背景颜色
selectedColor: {
type: String,
default: "#fff"
}, // 选中文本颜色 | 未选中背景颜色
type: {
type: String,
default: "button",
}, // tab item 样式类型 button|text
},
data() {
return {
}
},
watch: {
type: {
handler: function(n) {
if (!n || (n != 'button' && n != 'text')) {
this.type = 'button'
}
},
immediate: true
},
direction: {
handler: function(n) {
if (!n || (n != "row" && n != "column")) {
this.direction = "row"
}
},
immediate: true
}
},
computed: {
getCount() {
if (this.item.count) {
let count = Number(this.item.count);
if (count > 99) count = 99;
return count;
}
return this.item.count
},
showUnderLine() {
return this.type == "text" && this.isCurrent;
},
textColor: function() {
let color;
if (this.type == 'button') {
color = this.isCurrent ? this.selectedColor : this.color
} else {
color = this.isCurrent ? this.color : "#333"
}
return color;
},
tabItemStyle: function() {
let color = this.textColor;
let bgColor;
if (this.type == 'button') {
bgColor = this.isCurrent ? this.color : this.selectedColor;
} else {
bgColor = "#fff";
}
return {
color,
backgroundColor: bgColor
}
},
tabItemClass: function() {
return [
this.isCurrent && this.type == 'text' ? "current-tab-item_text" : "",
this.isCurrent && this.type == 'button' ? "current-tab-item" : "",
this.direction == 'row' ? "row-tab-item" : "",
this.direction == 'column' ? "column-tab-item" : ""
]
},
iconClass: function() {
return [
this.direction == 'row' ? "row-tab-icon" : "",
this.direction == 'column' ? "column-tab-icon" : ""
]
}
},
methods: {
tap() {
this.$emit('tap')
}
}
}
</script>
<style lang="scss">
.c-tab-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
.title {
font-size: 32rpx;
font-weight: 600;
text-align: center;
position: relative;
.count {
position: absolute;
right: 0;
top: 0;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
text-align: center;
line-height: 40rpx;
transform: translate(100%, -50%);
background: #ff2525;
color: #fff;
font-size: 16rpx;
}
}
.icon {}
.row-tab-icon {
margin: 0;
margin-right: 8rpx;
}
.column-tab-icon {
margin: 0;
margin-bottom: 8rpx;
}
.under-line {
position: absolute;
left: 50%;
bottom: 2px;
transform: translateX(-50%);
width: 50%;
height: 10rpx;
border-radius: 5px;
}
}
.current-tab-item {
background: #FECF02;
color: #fff;
}
.current-tab-item_text {
font-weight: 700;
}
.row-tab-item {
flex-direction: row;
}
.column-tab-item {
flex-direction: column;
}
</style>
\ No newline at end of file
<template>
<view class="c-tabs" :class="tabsClass">
<cTabItem style='height: 100%;' :style="[tabItemStyle]" v-for='(item, index) in tabs' :key='index' :item='item'
:isCurrent='index==currentIndex' :direction='itemDirection' :color='color' :selectedColor='selectedColor'
:type="type" :nameKey="nameKey" @tap='tapItem(index)'></cTabItem>
</view>
</template>
<script>
import cTabItem from "./c-tab-item/c-tab-item.vue";
export default {
components: {
cTabItem
},
name: "c-tabs",
props: {
isIndexValue: {
type: Boolean,
default: true,
}, // 用以tabs对应key的value值为index时, 设置为false, 优先比对key对应的value值
value: {
type: Number | String,
default: 0
}, // 当前选中值,index 或者 valueKey对应的值,类型为number时,默认优先比对index
tabs: {
type: Array,
default: function() {
return []
}
}, // 列项 {title: "", value: "", icon: "", iconPrefix: ""}
nameKey: {
type: String,
default: "title",
}, // tabs 为 object 时, 显示字段
valueKey: {
type: String,
default: "value"
}, // tabs 为 object 时, 指定获取value的key值
itemDirection: {
type: String,
default: "row"
}, // item 方向 row | column
color: {
type: String,
default: "#FECF02"
}, // 未选中文本颜色 | 选中背景颜色
selectedColor: {
type: String,
default: "#fff"
}, // 选中文本颜色 | 未选中背景颜色
type: {
type: String,
default: "button"
}, // tab item 样式类型 button|text
showShadow: {
type: Boolean,
default: true
}, // 是否显示阴影
autoWidth: {
type: Boolean,
default: false
},
minWidth: {
type: Number,
default: 120
}
},
data() {
return {
currentIndex: 0
};
},
computed: {
tabItemStyle: function() {
return this.autoWidth ? {
minWidth: `${this.minWidth?this.minWidth: 140}rpx`
} : {
flex: 1
}
},
tabsClass: function() {
return [
// this.showShadow ? "c-tabs_box-shadow" : ""
this.showShadow ? "" : ""
]
}
},
watch: {
type: {
handler: function(n) {
if (!n || (n != 'button' || n != 'text')) {
this.type = 'button'
}
},
immediate: true
},
value: {
handler: function(n) {
this.resetCurrentIndex()
},
immediate: true
},
tabs: {
handler: function(n) {
if (!n || !(n instanceof Array)) {
this.tabs = []
} else {
this.resetCurrentIndex()
}
},
immediate: true
},
valueKey: {
handler: function(n) {
if (!n) {
this.valueKey = 'value'
}
this.resetCurrentIndex()
},
immediate: true
},
currentIndex: function(n) {
this.$emit('change', {
index: n,
value: this.tabs[n][this.valueKey],
item: this.tabs[n]
})
}
},
methods: {
resetCurrentIndex() {
if (typeof this.value == 'number' && this.isIndexValue) {
this.currentIndex = this.value;
} else {
let cIndex = 0;
this.tabs.forEach((item, index) => {
if (item[this.valueKey] == this.value) {
cIndex = index;
}
})
this.currentIndex = cIndex;
}
},
tapItem(index) {
this.currentIndex = index;
}
}
}
</script>
<style lang="scss">
.c-tabs {
width: 100%;
height: 80rpx;
display: flex;
flex-direction: row;
align-items: flex-start;
}
.c-tabs_box-shadow {
box-shadow: 0 20rpx 20rpx #88888844;
}
</style>
\ No newline at end of file
<template>
<view>
<uni-popup type="bottom" ref="select">
<view
:style="'width: '+ windowWidth +'px;height:860rpx;background-color: #202020; border-radius:20rpx 20rpx 0 0;'">
<view style="height: 70rpx;display: flex; flex-direction: row;align-items: center;">
<image src="@/static/video/layers.png" style="width: 40rpx; height: 40rpx; margin-left: 20rpx;" />
<view
:style="'font-size: 30rpx; font-weight: bold; color: #FFFFFF; margin-left: 20rpx; overflow: hidden;'">
当前播放第{{ Number(originIndex+1) }}
</view>
<image @click="down" src="@/static/video/down.png"
style="width: 50rpx; height: 50rpx; margin:0 20rpx 0 auto; ">
</image>
</view>
<u-tabs v-if="showTab" :current="myTabIndex" :list="tabs" @change="popHandleChange" lineWidth="30"
lineColor="#f56c6c" :activeStyle="{
color: '#f56c6c',
fontWeight: 'bold',
transform: 'scale(1.05)'
}" :inactiveStyle="{
color: '#ffffff',
transform: 'scale(1)'
}"></u-tabs>
<swiper class="swiper mt-20" @change="popSwiperChange" :current="myTabIndex" :vertical="false" circular>
<swiper-item v-for="(list, index1) in subList" :key="index1">
<scroll-view :style="'width: '+ (windowWidth) +'px; height: '+ ((windowHeight/1.6)*0.85) +'px;'"
:scroll-y="true">
<view class="flex space" style="display:flex; flex-wrap:wrap;">
<block v-for="(list,index2) in subList[index1]">
<view @click.stop="selectThisVideo(index2 + (index1%30*30))"
style="position: relative;width:16%;height: 120rpx; background: gray;border-radius: 10rpx;margin-left: 5rpx;margin-bottom: 5rpx;">
<image v-if="originList[
(index1 == 0 ? index2 : (index2 + (index1%30*30)))
].lock" src="@/static/video/lock.png"
style="width:30rpx;height:30rpx;position:absolute;right:5rpx;top:5rpx;" />
<view class="flex"
style="width:100%;height:100%;align-items:center;justify-content:center;">
<text v-if="(originIndex+1)==list.vedioIndex"
style="font-size: 36rpx;color: #f56c6c;">{{list.vedioIndex}}</text>
<text v-else
style="font-size: 36rpx;color: #FFFFFF;">{{list.vedioIndex}}</text>
</view>
<image v-if="(originIndex+1)==list.vedioIndex"
src="https://mints-pkg.oss-cn-beijing.aliyuncs.com/pkg/img/ic_playing.gif"
style="width:30rpx;height:30rpx;position:absolute;left:5rpx;bottom:5rpx;" />
</view>
</block>
</view>
</scroll-view>
</swiper-item>
</swiper>
</view>
</uni-popup>
</view>
</template>
<script>
export default {
name: 'episodePopup',
props: {
windowWidth: Number,
windowHeight: Number,
originIndex: Number,
tabIndex: Number,
tabs: Array,
subList: Array,
originList: Array,
},
data() {
return {
myTabIndex: this.tabIndex,
showTab: false
};
},
methods: {
show() {
this.$refs.select.open();
this.showTab = true
},
popHandleChange(event) {
this.myTabIndex = event.index
},
popSwiperChange(event) {
const {
current
} = event.detail;
this.myTabIndex = current
},
down() {
this.$refs.select.close();
},
selectThisVideo(index) {
this.$emit('selectVideo', index);
this.$refs.select.close();
}
}
}
</script>
<style>
.swiper {
width: 100%;
height: 100%;
}
</style>
\ No newline at end of file
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.
import Book from "../../../common/models/Book.js";
export default class WarehouseBookItem extends Book {
constructor(param) {
super(param || {});
const {} = param || {}
}
}
\ No newline at end of file
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