feat: 添加静态资源文件及基础组件
- 新增大量图片资源,包括图标、背景图、海报等,用于页面展示 - 添加字体文件及样式配置,支持自定义字体 - 引入 uni-ui 组件库,包括图标、弹窗、日期选择器等常用组件 - 添加网络状态监听、加密工具等基础工具函数 - 配置 Vuex 状态管理,包括购物车数量、热门搜索词等 - 增加页面组件,如用户协议、分销信息、提现状态等 - 配置项目构建和忽略文件,优化开发体验
This commit is contained in:
395
components/BaseCalendar.vue
Normal file
395
components/BaseCalendar.vue
Normal file
@@ -0,0 +1,395 @@
|
||||
<template>
|
||||
<!-- 打卡日历页面 -->
|
||||
<view class="all">
|
||||
<view class="bar">
|
||||
<!-- 上一个月 -->
|
||||
<view class="previous barbtn" @click="changeMonth(-1)">
|
||||
<view class="iconfont icon-ic_left2"></view>
|
||||
</view>
|
||||
<!-- 显示年月 -->
|
||||
<view class="date">{{ nowYear || '--' }} 年 {{ nowMonth || '--' }} 月</view>
|
||||
<!-- 下一个月 -->
|
||||
<view class="next barbtn" @click="changeMonth(1)">
|
||||
<view class="iconfont icon-ic_right2"></view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 显示星期 -->
|
||||
<view class="week-area">
|
||||
<view class="week-txt" v-for="(item, index) in weeksTxt[langType]" :key="index">{{ item }}</view>
|
||||
</view>
|
||||
|
||||
<view class="myDateTable">
|
||||
<view v-for="(item, j) in calendarDays" :key="j" class="dateCell">
|
||||
<view v-if="item.date == undefined || item.date == null" class="cell"></view>
|
||||
<template v-else>
|
||||
<!-- 已签到日期 -->
|
||||
<view v-if="item.isSign == true" class="cell signColor bgWhite">
|
||||
<!-- {{ item.date }} -->
|
||||
<image class="sign-today" src="@/pages/users/static/sign-today.png" mode=""></image>
|
||||
<view class="sign">已签</view>
|
||||
</view>
|
||||
<!-- 漏签 -->
|
||||
<view @click="clickSign(item.date, 0)" class="cell outSignStyle" v-else-if="item.isBeforeToday && item.isThisMonth">
|
||||
<!-- redColor bgGray -->
|
||||
{{ item.date }}
|
||||
<!-- <view class="redDot"></view> -->
|
||||
</view>
|
||||
<!-- 今日未签到-->
|
||||
<view @click="clickSign(item.date, 1)" class="cell whiteColor" v-else-if="item.date == today && nowMonth == toMonth && nowYear == toYear">今</view>
|
||||
<!-- 当前日期之后 -->
|
||||
<view class="whiteColor cell" v-else>
|
||||
{{ item.date }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
calendarDays: [],
|
||||
SignData: [], // 已经签到的数据
|
||||
nowYear: 0, //当前选的年
|
||||
nowMonth: 0, //当前选的月
|
||||
today: parseInt(new Date().getDate()), //系统本日
|
||||
toMonth: parseInt(new Date().getMonth() + 1), //系统本月
|
||||
toYear: parseInt(new Date().getFullYear()), //系统本年
|
||||
weeksTxt: {
|
||||
ch: ['日', '一', '二', '三', '四', '五', '六'],
|
||||
en: ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat']
|
||||
}
|
||||
};
|
||||
},
|
||||
props: {
|
||||
isReplenishSign: {
|
||||
// 是否允许过期补签
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isFullCalendar: {
|
||||
// 是否需要填满日历,前后空的格子填入上/下个月的日期
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
yearMonth: {
|
||||
// 2022-01 这种格式,默认当前年月
|
||||
type: String,
|
||||
default: new Date().getFullYear() + '-' + new Date().getMonth() + 1
|
||||
},
|
||||
dataSource: {
|
||||
//已签到的数据源,例: 5、6号已签到: ["2022-01-05","2022-01-06"],兼容个位数前可加可不加0
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
langType: {
|
||||
//只是示例一个翻译而已,要想所有都翻译自己可以再加加
|
||||
type: String,
|
||||
default: 'ch' //en
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!/en|ch/g.test(this.langType)) {
|
||||
this.langType = 'ch'; // 非中英,则固定中文
|
||||
}
|
||||
const ymArr = this.yearMonth.split('-');
|
||||
this.buildCalendar(ymArr[0], ymArr[1]);
|
||||
this.onSignDataChange(this.dataSource);
|
||||
},
|
||||
watch: {
|
||||
dataSource: 'onSignDataChange'
|
||||
},
|
||||
methods: {
|
||||
clickSign(date, type) {
|
||||
//type=0补签,type=1当日签到
|
||||
var strTip = '签到';
|
||||
|
||||
if (type == 0) {
|
||||
if (!this.isReplenishSign) {
|
||||
// 未开启补签,阻止继续执行
|
||||
console.log('————补签功能未开启————');
|
||||
return;
|
||||
}
|
||||
strTip = '补签';
|
||||
}
|
||||
uni.showToast({
|
||||
title: date + '号' + strTip + '成功',
|
||||
icon: 'success',
|
||||
position: 'bottom'
|
||||
});
|
||||
this.$emit('clickChange', this.nowYear + '-' + this.nowMonth + '-' + date); //传给调用模板页面
|
||||
// 父页面要在clickChange里去修改输入的数据源dataSource,否则视图不更新的!
|
||||
},
|
||||
//构建日历数据
|
||||
buildCalendar(y, m) {
|
||||
this.nowYear = y;
|
||||
this.nowMonth = m;
|
||||
this.calculateEmptyGrids(y, m);
|
||||
this.calculateDays(y, m);
|
||||
if (this.isFullCalendar) {
|
||||
this.fullCell();
|
||||
}
|
||||
},
|
||||
// 监听到签到数据源改变
|
||||
onSignDataChange(newData, oldData = []) {
|
||||
this.SignData = newData;
|
||||
this.matchSign();
|
||||
},
|
||||
//匹配标记已经签到的日子
|
||||
matchSign() {
|
||||
var signs = this.SignData;
|
||||
var daysArr = this.calendarDays;
|
||||
for (var i = 0; i < signs.length; i++) {
|
||||
var current = new Date(this.toIOSDate(signs[i])).getTime(); // ios只认/格式的日期
|
||||
for (var j = 0; j < daysArr.length; j++) {
|
||||
if (current == new Date(this.toIOSDate(daysArr[j].fullDate)).getTime()) {
|
||||
daysArr[j].isSign = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.calendarDays = daysArr;
|
||||
// console.log(this.calendarDays );
|
||||
},
|
||||
// 计算当月1号前空了几个格子,把它填充在calendarDays数组的前面
|
||||
calculateEmptyGrids(year, month) {
|
||||
//计算每个月时要清零
|
||||
this.calendarDays = [];
|
||||
const firstDayOfWeek = this.getFirstDayOfWeek(year, month);
|
||||
if (firstDayOfWeek > 0) {
|
||||
for (let i = 0; i < firstDayOfWeek; i++) {
|
||||
this.calendarDays.push({
|
||||
date: null, // 显示的日期
|
||||
fullDate: null, // 日期yyyy-mm-dd格式
|
||||
isBeforeToday: true, // 今日之前
|
||||
isSign: false, // 是否签到
|
||||
isThisMonth: false // 是本月
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
// 绘制当月天数占的格子,并把它放到days数组中
|
||||
calculateDays(year, month) {
|
||||
const thisMonthDays = this.getMonthDayLength(year, month);
|
||||
const toDate = new Date(this.toYear + '/' + this.toMonth + '/' + this.today);
|
||||
for (let i = 1; i <= thisMonthDays; i++) {
|
||||
const fullDate = year + '-' + month + '-' + i;
|
||||
const isBeforeToday = new Date(this.toIOSDate(fullDate)) < toDate;
|
||||
this.calendarDays.push({
|
||||
date: i,
|
||||
fullDate,
|
||||
isBeforeToday,
|
||||
isSign: false,
|
||||
isThisMonth: true
|
||||
});
|
||||
}
|
||||
//console.log(this.calendarDays);
|
||||
},
|
||||
// 切换控制年月,上一个月,下一个月
|
||||
changeMonth(type) {
|
||||
console.log(type)
|
||||
const nowYear = parseInt(this.nowYear);
|
||||
const nowMonth = parseInt(this.nowMonth);
|
||||
const newObj = this.getOperateMonthDate(nowYear, nowMonth, type);
|
||||
this.buildCalendar(newObj.year, newObj.month); // 重新构建日历数据
|
||||
this.$emit('dateChange', this.nowYear + '-' + this.nowMonth); //传给调用模板页面去拿新数据
|
||||
},
|
||||
// 获取当月共多少天,也就是获取月的最后一天
|
||||
getMonthDayLength(year, month) {
|
||||
return new Date(year, month, 0).getDate();
|
||||
},
|
||||
// 获取当月第一天星期几
|
||||
getFirstDayOfWeek(year, month, day = 1) {
|
||||
return new Date(Date.UTC(year, month - 1, day)).getDay();
|
||||
},
|
||||
toIOSDate(strDate) {
|
||||
// iso不认识"-"拼接的日期,所以转/
|
||||
return strDate ? strDate.replace(/-/g, '/') : strDate;
|
||||
},
|
||||
// 需要填满格子,上/下个月的日期拉出来填满格子
|
||||
fullCell() {
|
||||
const endDay = this.getMonthDayLength(this.nowYear, this.nowMonth);
|
||||
const beforeEmptyLength = this.getFirstDayOfWeek(this.nowYear, this.nowMonth);
|
||||
const afterEmptyLength = 6 - this.getFirstDayOfWeek(this.nowYear, this.nowMonth, endDay);
|
||||
|
||||
const last = this.getOperateMonthDate(this.nowYear, this.nowMonth, -1);
|
||||
const lastMonthEndDay = this.getMonthDayLength(last.year, last.month);
|
||||
for (let i = 0; i < beforeEmptyLength; i++) {
|
||||
const date = lastMonthEndDay - beforeEmptyLength + i + 1;
|
||||
this.calendarDays[i].date = date;
|
||||
this.calendarDays[i].fullDate = last.year + '-' + last.month + '-' + date;
|
||||
}
|
||||
const next = this.getOperateMonthDate(this.nowYear, this.nowMonth, 1);
|
||||
for (let i = 1; i <= afterEmptyLength; i++) {
|
||||
this.calendarDays.push({
|
||||
date: i, // 显示的日期
|
||||
fullDate: next.year + '-' + next.month + '-' + i, // 日期yyyy-mm-dd格式
|
||||
isBeforeToday: false, // 今日之前
|
||||
isSign: false, // 是否签到
|
||||
isThisMonth: false // 是本月
|
||||
});
|
||||
}
|
||||
// console.log(beforeEmptyLength,afterEmptyLength,lastMonthEndDay);
|
||||
|
||||
// console.log(this.calendarDays);
|
||||
},
|
||||
// 获取加/减一个月的日期
|
||||
getOperateMonthDate(yy, mm, num) {
|
||||
let month = parseInt(mm) + parseInt(num);
|
||||
let year = parseInt(yy);
|
||||
if (month > 12) {
|
||||
month = 1;
|
||||
year++;
|
||||
} else if (month < 1) {
|
||||
month = 12;
|
||||
year--;
|
||||
}
|
||||
return {
|
||||
month,
|
||||
year
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.all {
|
||||
|
||||
// margin-top: 20rpx;
|
||||
.cir-bg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 324rpx;
|
||||
height: 230rpx;
|
||||
background: #fee3de;
|
||||
opacity: 1;
|
||||
filter: blur(133px);
|
||||
}
|
||||
}
|
||||
|
||||
.all .bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 60rpx 64rpx 64rpx;
|
||||
}
|
||||
|
||||
.bar .barbtn {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
background: rgba(233, 51, 35, 0.1);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
|
||||
.iconfont {
|
||||
margin: auto;
|
||||
color: rgba(233, 51, 35, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.all .week-area {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px 0;
|
||||
box-sizing: border-box;
|
||||
width: 91vw;
|
||||
margin: 10px auto;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.all .week-txt {
|
||||
text-align: center;
|
||||
width: 88rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 34rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.myDateTable {
|
||||
margin: 0 auto;
|
||||
width: 91vw;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.myDateTable .dateCell {
|
||||
width: 13vw;
|
||||
padding: 1vw;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
font-size: 36rpx;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dateCell .cell {
|
||||
display: flex;
|
||||
border-radius: 16rpx;
|
||||
height: 120rpx;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
font-family: 'SemiBold';
|
||||
|
||||
.sign-today {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.sign {
|
||||
font-size: 18rpx;
|
||||
font-weight: 500;
|
||||
color: #e93323;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.whiteColor {
|
||||
color: #981007;
|
||||
}
|
||||
|
||||
.signColor {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bgWhite {
|
||||
background-color: #fceae9;
|
||||
}
|
||||
|
||||
.bgGray {
|
||||
background-color: rgba(255, 255, 255, 0.42);
|
||||
}
|
||||
|
||||
.bgBlue {
|
||||
font-size: 14px;
|
||||
background-color: #4b95e6;
|
||||
}
|
||||
|
||||
.redColor {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.outSignStyle {
|
||||
// border: 1px solid #981007;
|
||||
color: #981007;
|
||||
}
|
||||
|
||||
.redDot {
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
border-radius: 16rpx;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 36rpx;
|
||||
font-family: PingFang SC-Medium, PingFang SC;
|
||||
font-weight: 500;
|
||||
color: #981007;
|
||||
}
|
||||
</style>
|
||||
163
components/BaseCode.vue
Normal file
163
components/BaseCode.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<view class="codePopup tui-modal__container" :class="[isShowCode ? 'tui-modal-show' : '']" @touchmove.stop.prevent="moveHandle">
|
||||
<view class="tui-modal-box" :class="[isShowCode ? 'tui-modal-normal' : 'tui-modal-scale', isShowCode ? 'tui-modal-show' : '']">
|
||||
<view class="header acea-row row-between-wrapper">
|
||||
<view class="title on">员工推广码</view>
|
||||
</view>
|
||||
<view>
|
||||
<view class="acea-row row-center-wrapper mb-20 mt-28">
|
||||
<!-- #ifndef MP -->
|
||||
<w-qrcode :options="config.qrc"></w-qrcode>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<image class="code-img" :src="codeImg" show-menu-by-longpress></image>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="tip">如遇到扫码失败</view>
|
||||
<view class="tip">请将屏幕调至最亮重新扫码</view>
|
||||
</view>
|
||||
<view class="close">
|
||||
<view class="iconfont icon-ic_close1" @click="close"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mark" v-if="isShowCode" @touchmove.stop.prevent="moveHandle"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'codeModal',
|
||||
props: {
|
||||
isShowCode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
code: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
codeImg: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
config: {
|
||||
bar: {
|
||||
code: '',
|
||||
color: ['#000'],
|
||||
bgColor: '#FFFFFF', // 背景色
|
||||
width: 480, // 宽度
|
||||
height: 110 // 高度
|
||||
},
|
||||
qrc: {
|
||||
code: '123123',
|
||||
size: 380, // 二维码大小
|
||||
level: 3, //等级 0~4
|
||||
bgColor: '#FFFFFF', //二维码背景色 默认白色
|
||||
color: ['#333', '#333'] //边框颜色支持渐变色
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
code(newVal) {
|
||||
// #ifdef MP
|
||||
this.config.qrc.code = newVal;
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log(this.code);
|
||||
// #ifndef MP
|
||||
this.config.qrc.code = this.code;
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
moveHandle() {},
|
||||
close() {
|
||||
this.$emit('update:isShowCode', false);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.mark {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 50;
|
||||
}
|
||||
.tui-modal__container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
visibility: hidden;
|
||||
z-index: 51;
|
||||
}
|
||||
|
||||
.tui-modal-box {
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease-in-out;
|
||||
width: 600rpx;
|
||||
padding: 40rpx;
|
||||
border-radius: 32rpx;
|
||||
background-color: #fff;
|
||||
z-index: 9999;
|
||||
.tip {
|
||||
color: #999999;
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.close {
|
||||
width: 520rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
bottom: -80rpx;
|
||||
.icon-ic_close1 {
|
||||
font-size: 52rpx;
|
||||
color: #cccccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tui-modal-scale {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.tui-modal-normal {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.tui-modal-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.codePopup .header .title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
}
|
||||
.code-img{
|
||||
width: 380rpx;
|
||||
height: 380rpx;
|
||||
}
|
||||
</style>
|
||||
124
components/BaseMoney.vue
Normal file
124
components/BaseMoney.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<view :class="{ line: line, weight: weight }"
|
||||
:style="{color:color, fontFamily: SemiBold ? 'SemiBold' : 'Regular'}"
|
||||
class="base-money">
|
||||
<text v-show="preFix" class="preFix" :style="{'font-size': preFixSize + 'rpx','color':textColor}">{{preFix}}</text>
|
||||
<text class="symbol" :style="{'font-size': symbolSize +'rpx'}">¥</text><text class="integer"
|
||||
:style="{'font-size': integerSize +'rpx'}">{{ integer }}</text>
|
||||
<text v-if="digits && showDigits" class="decimal"
|
||||
:style="{'font-size': decimalSize +'rpx'}">.{{ decimal }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseMoney',
|
||||
props: {
|
||||
// 小数位数,为0则不显示
|
||||
digits: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
money: {
|
||||
type: String | Number,
|
||||
default: ""
|
||||
},
|
||||
// 删除线
|
||||
line: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 粗体
|
||||
weight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
color:{
|
||||
type: String,
|
||||
default: 'var(--view-theme)'
|
||||
},
|
||||
textColor:{
|
||||
type: String,
|
||||
default: '#999'
|
||||
},
|
||||
symbolSize: {
|
||||
type: String,
|
||||
default: '20'
|
||||
},
|
||||
integerSize: {
|
||||
type: String,
|
||||
default: '26'
|
||||
},
|
||||
decimalSize: {
|
||||
type: String,
|
||||
default: '24'
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
preFix:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
preFixSize:{
|
||||
type: String,
|
||||
default: '24'
|
||||
},
|
||||
SemiBold:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isCoupon:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
integer: 0,
|
||||
decimal: 0,
|
||||
showDigits:false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
money: {
|
||||
handler(newValue, oldValue) {
|
||||
let value = Number(newValue).toFixed(this.digits);
|
||||
value = value.split('.');
|
||||
this.integer = value[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
if (value[1]) {
|
||||
this.decimal = value[1];
|
||||
if((this.isCoupon && this.decimal != '00') || !this.isCoupon) {
|
||||
this.showDigits = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.base-money {
|
||||
display: inline-block;
|
||||
&.line {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
&.weight {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
.preFix{
|
||||
font-weight: 500 !important;
|
||||
font-family: PingFang SC-Medium, PingFang SC !important;
|
||||
}
|
||||
.SemiBold{
|
||||
font-family:'SemiBold';
|
||||
}
|
||||
.Regular{
|
||||
font-family:'Regular';
|
||||
}
|
||||
</style>
|
||||
128
components/BaseNumberRun.vue
Normal file
128
components/BaseNumberRun.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<!-- 数字滚动效果 -->
|
||||
<template>
|
||||
<view class="pengke-scroll" :style="[penkeStyle]">
|
||||
<view v-for="(item, index) in digitalData" :key="index"
|
||||
:class="{'digital': true, 'digital-str': isNaN(item.num)}">
|
||||
<!-- 符号显示 -->
|
||||
<view v-if="isNaN(item.num)">{{item.num}}</view>
|
||||
<!-- 滚动的列表 -->
|
||||
<view v-else class="scroll-num">
|
||||
<view class="tra-num" :style="item.style">0123456789</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// 数值
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: '9.99'
|
||||
},
|
||||
|
||||
// 字体大小
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: '14'
|
||||
},
|
||||
|
||||
// 字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
|
||||
// 文字居中
|
||||
textAlign: {
|
||||
type: String,
|
||||
default: 'center' // left, center, right
|
||||
},
|
||||
//动画时间
|
||||
time: {
|
||||
type: Number,
|
||||
default: 2
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
digitalData: []
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
penkeStyle(){
|
||||
return {
|
||||
'font-size': this.size + 'px',
|
||||
'color': this.color,
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(val) {
|
||||
const digitalArr = String(val).split('')
|
||||
const dataList = []
|
||||
digitalArr.forEach((num) => {
|
||||
const obj = {
|
||||
num: isNaN(num) ? num : Number(num),
|
||||
style: ''
|
||||
}
|
||||
dataList.push(obj)
|
||||
})
|
||||
this.digitalData = dataList
|
||||
this.setScrollNum()
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 滚动数字
|
||||
setScrollNum() {
|
||||
const defData = JSON.parse(JSON.stringify(this.digitalData))
|
||||
defData.forEach((item, index) => {
|
||||
// 设置移动距离
|
||||
item.style = `transform: translateY(-${item.num}em);transition:all ${this.time}s;`
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.digitalData = defData
|
||||
}, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pengke-scroll {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.digital {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 0.5em; // 文本间隔
|
||||
height: 1em;
|
||||
line-height: 0.7em;
|
||||
overflow: hidden;
|
||||
|
||||
.scroll-num {
|
||||
// 文本竖直排列
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: upright;
|
||||
|
||||
.tra-num {
|
||||
transition: all 1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.digital-str {
|
||||
width: auto;
|
||||
line-height: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
88
components/BaseTag.vue
Normal file
88
components/BaseTag.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<view class="base-tag" :class="{middle: size == 'middle'}" v-if="!imgSrc" :style="[tagStyle]" >{{text}}</view>
|
||||
<image class="img-tag" :class="{'middle-img-tag': size == 'middle'}" :src="imgSrc" mode="heightFix" v-else></image>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tag",
|
||||
props: {
|
||||
size: {
|
||||
// 标签大小 normal, small
|
||||
type: String,
|
||||
default: "normal"
|
||||
},
|
||||
// 标签内容
|
||||
text: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
circle: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: '#e93323'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
borderColor:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
imgSrc:{
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
tagStyle(){
|
||||
return {
|
||||
background: this.background,
|
||||
color: this.color,
|
||||
border: this.borderColor ? `1rpx solid ${this.borderColor}` : 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.base-tag {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 30rpx;
|
||||
font-size: 18rpx;
|
||||
padding: 0 8rpx;
|
||||
color: #333;
|
||||
border-radius: 4rpx;
|
||||
background-color: #fff;
|
||||
margin-right: 8rpx;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.middle{
|
||||
height: 36rpx;
|
||||
padding: 0 12rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
.img-tag{
|
||||
display: block;
|
||||
height: 30rpx;
|
||||
border-radius: 4rpx;
|
||||
margin-right: 14rpx;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.middle-img-tag{
|
||||
height: 36rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
</style>
|
||||
414
components/BaseTextMore.vue
Normal file
414
components/BaseTextMore.vue
Normal file
@@ -0,0 +1,414 @@
|
||||
<template>
|
||||
<view class="t-wrap">
|
||||
<!-- 虚拟view用于计算,计算完成则消失 -->
|
||||
<view class="t-txt-hide" :id="hid" v-if="!isCompute" :style="[computeStyle(0),{'text-align':oneRowTextAlign}]">
|
||||
<text space="nbsp">{{testContent?testContent:content}}{{showSymbol?'...':''}}</text><text
|
||||
v-if="expandText && collapseText && showSymbol" class="t-button">
|
||||
{{expandText}}
|
||||
</text><text></text>
|
||||
</view>
|
||||
<!-- 真实显示的内容 -->
|
||||
<view class="t-ellipsis" :id="id"
|
||||
:style="[!isCompute?computeStyle(1):computeStyle(2),{'text-align':oneRowTextAlign}]" @click="contentClick">
|
||||
<text space="nbsp">{{(!isCompute || expand)?content:(actualContent+(showSymbol?'...':''))}}</text><text
|
||||
v-if="expandText && collapseText && showSymbol" class="t-button" @click.stop="changeCollapse"
|
||||
:style="{'color':actionFontColor}">{{!expand?expandText:collapseText}}</text><text></text>
|
||||
</view>
|
||||
<!-- 这里加入了自定义的动态骨架屏友好反馈 -->
|
||||
<view v-if="!isCompute && rows>0" class="t-skeleton">
|
||||
<view class="skeletons" v-for="(item,index) in rows" :key="index">
|
||||
<view class="empty"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//为了兼容部分老机型,增加扰动因子参数
|
||||
const factor = 5;
|
||||
export default {
|
||||
name: "KevyEllipsis",
|
||||
props: {
|
||||
/**
|
||||
* 文本唯一标识符,非必填
|
||||
*/
|
||||
textId: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 文本内容,默认''
|
||||
*/
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 字体大小,单位rpx,默认28
|
||||
*/
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: 28
|
||||
},
|
||||
/**
|
||||
* 字体颜色,默认#666666
|
||||
*/
|
||||
fontColor: {
|
||||
color: String,
|
||||
default: '#666666'
|
||||
},
|
||||
/**
|
||||
* 收起操作的文案,默认''
|
||||
*/
|
||||
collapseText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 展开操作的文案,默认''
|
||||
*/
|
||||
expandText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 收起、展开操作文字颜色,默认'#007aff'
|
||||
*/
|
||||
actionFontColor: {
|
||||
color: String,
|
||||
default: '#007aff'
|
||||
},
|
||||
/**
|
||||
* 展示行数,默认1
|
||||
*/
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
/**
|
||||
* 只有一行时文本对齐方式,支持left、right、justify
|
||||
*/
|
||||
oneRowTextAlign: {
|
||||
type: String,
|
||||
default: "justify"
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
//是否展开
|
||||
expand: false,
|
||||
//是否已计算
|
||||
isCompute: false,
|
||||
//内容高度
|
||||
h: undefined,
|
||||
//内容宽度
|
||||
w: undefined,
|
||||
//实际显示内容
|
||||
actualContent: '',
|
||||
//高度探测内容
|
||||
testContent: undefined,
|
||||
//是否显示省略号
|
||||
showSymbol: false,
|
||||
//hid和id,唯一标识符
|
||||
hid: 'hid' + Math.random().toString(36).substr(2),
|
||||
id: 'id' + Math.random().toString(36).substr(2),
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.initEllipsis();
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
//动态计算组件样式
|
||||
computeStyle() {
|
||||
return b => {
|
||||
let lines = this.rows > 0 ? this.rows : 1;
|
||||
let obj = {};
|
||||
if (b == 1) {
|
||||
obj = {
|
||||
'-webkit-line-clamp': lines,
|
||||
'display': '-webkit-box',
|
||||
'text-overflow': 'ellipsis',
|
||||
'overflow': 'hidden',
|
||||
'-webkit-box-orient': 'vertical'
|
||||
};
|
||||
} else if (b == 2) {
|
||||
obj = {
|
||||
'position': 'relative',
|
||||
'left': '0rpx',
|
||||
...obj
|
||||
};
|
||||
}
|
||||
return {
|
||||
'font-size': this.fontSize + 'rpx',
|
||||
'color': this.fontColor,
|
||||
...obj
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
content(newVal, oldVal) {
|
||||
this.expand=false;
|
||||
this.isCompute=false;
|
||||
this.h=undefined;
|
||||
this.w=undefined;
|
||||
this.actualContent='';
|
||||
this.showSymbol=false;
|
||||
this.initEllipsis();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//初始化
|
||||
initEllipsis() {
|
||||
if (this.content?.length > 0) {
|
||||
this.$nextTick(() => {
|
||||
this.init(this, () => {
|
||||
this.compute(this);
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
//收起展开状态切换
|
||||
changeCollapse() {
|
||||
this.expand = !this.expand;
|
||||
},
|
||||
//文本点击事件
|
||||
contentClick() {
|
||||
this.$emit('contentClick', this.textId);
|
||||
},
|
||||
//组件参数初始化
|
||||
init($this, callback, isali) {
|
||||
if (isali) {
|
||||
uni.createSelectorQuery().in().select('#' + $this.id).boundingClientRect(d => {
|
||||
$this.h = Number(d.height.toFixed(1));
|
||||
$this.w = Number(d.width.toFixed(1));
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
}).exec();
|
||||
} else {
|
||||
uni.createSelectorQuery().in($this).select('#' + $this.id).boundingClientRect(d => {
|
||||
$this.h = Number(d.height.toFixed(1));
|
||||
$this.w = Number(d.width.toFixed(1));
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
}).exec();
|
||||
}
|
||||
},
|
||||
//动态计算组件内容
|
||||
computeContent($this, isali, dr) {
|
||||
$this.$nextTick(() => {
|
||||
$this.getH($this, isali, (ch) => {
|
||||
if (ch - factor > $this.h) {
|
||||
if (dr === -1) {
|
||||
$this.testContent = $this.content.substring(0, $this.testContent.length -
|
||||
1);
|
||||
$this.computeContent($this, isali, dr);
|
||||
} else {
|
||||
$this.actualContent = $this.content.substring(0, $this.testContent.length -
|
||||
1);
|
||||
$this.isCompute = true;
|
||||
}
|
||||
} else {
|
||||
if (dr === -1) {
|
||||
$this.actualContent = $this.testContent;
|
||||
$this.isCompute = true;
|
||||
} else {
|
||||
$this.testContent = $this.content.substring(0, $this.testContent.length +
|
||||
1);
|
||||
$this.computeContent($this, isali, dr);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
//计算工具方法
|
||||
compute($this, isali) {
|
||||
let {
|
||||
rows,
|
||||
fontSize,
|
||||
content,
|
||||
h,
|
||||
w
|
||||
} = $this;
|
||||
$this.testContent = content;
|
||||
$this.$nextTick(() => {
|
||||
$this.getH($this, isali, (ch) => {
|
||||
if (ch - factor > h) {
|
||||
let lh = h / rows;
|
||||
let fn = Math.floor(w / $this.rpx2px(fontSize));
|
||||
let sfn = fn * rows;
|
||||
let i = $this.fontNum(content, sfn * 2 - ($this.expandText ? $this.fontNum(
|
||||
$this.expandText) :
|
||||
0) - 3);
|
||||
$this.showSymbol = true;
|
||||
$this.testContent = content.substring(0, i);
|
||||
$this.$nextTick(() => {
|
||||
$this.getH($this, isali, (ch1) => {
|
||||
if (ch1 - factor > h) {
|
||||
$this.testContent = content.substring(0, $this
|
||||
.testContent.length - 1);
|
||||
$this.computeContent($this, isali, -1);
|
||||
} else {
|
||||
$this.testContent = content.substring(0, $this
|
||||
.testContent.length + 1);
|
||||
$this.computeContent($this, isali, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$this.isCompute = true;
|
||||
$this.actualContent = content;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
//动态计算字符数
|
||||
fontNum(val, limit) {
|
||||
let c = 0;
|
||||
for (let i = 0; i < val.length; i++) {
|
||||
let a = val.charAt(i);
|
||||
if (a.match(/[^\x00-\xff]/ig) != null) {
|
||||
if (limit) {
|
||||
if (c + 2 > limit) {
|
||||
return i;
|
||||
} else {
|
||||
c += 2;
|
||||
}
|
||||
} else {
|
||||
c += 2;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (limit) {
|
||||
if (c + 1 > limit) {
|
||||
return i;
|
||||
} else {
|
||||
c += 1;
|
||||
}
|
||||
} else {
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!limit) {
|
||||
return c;
|
||||
}
|
||||
},
|
||||
rpx2px(rpx) {
|
||||
return uni.getWindowInfo().windowWidth * Number(rpx) / 750;
|
||||
},
|
||||
getH($, isali, callback) {
|
||||
if (isali) {
|
||||
uni.createSelectorQuery().in().select('#' + $.hid).fields({
|
||||
size: true
|
||||
}, d => {
|
||||
if(d && d.height){
|
||||
callback(Number(d.height.toFixed(1)));
|
||||
}
|
||||
}).exec();
|
||||
} else {
|
||||
uni.createSelectorQuery().in($).select('#' + $.hid).fields({
|
||||
size: true
|
||||
}, d => {
|
||||
if(d && d.height){
|
||||
callback(Number(d.height.toFixed(1)));
|
||||
}
|
||||
}).exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.t-wrap {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.t-txt-hide {
|
||||
box-sizing: border-box;
|
||||
word-break: break-all;
|
||||
position: absolute;
|
||||
top: 999999px;
|
||||
left: 999999px;
|
||||
z-index: -1000;
|
||||
top: 0rpx;
|
||||
width: 100%;
|
||||
margin: 0rpx;
|
||||
text-align: justify;
|
||||
white-space: pre-line;
|
||||
line-height: 1.5 !important;
|
||||
.t-button {
|
||||
float: right;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.t-ellipsis {
|
||||
text-align: justify;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
position: relative;
|
||||
left: 99999px;
|
||||
white-space: pre-line;
|
||||
line-height: 1.5 !important;
|
||||
.t-button {
|
||||
float: right;
|
||||
clear: both;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.t-skeleton {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0rpx;
|
||||
left: 0rpx;
|
||||
}
|
||||
|
||||
.skeletons:first-child {
|
||||
margin-top: 0rpx !important;
|
||||
}
|
||||
|
||||
.skeletons {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 32rpx;
|
||||
margin-top: 12rpx;
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.skeletons .empty {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-webkit-transform: translateX(-100%);
|
||||
transform: translateX(-100%);
|
||||
background: linear-gradient(90deg, transparent, rgba(216, 216, 216, 0.753), transparent);
|
||||
-webkit-animation: loading .8s infinite;
|
||||
animation: loading .8s infinite;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@keyframes loading {
|
||||
100% {
|
||||
-webkit-transform: translateX(100%);
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
60
components/Loading/index.vue
Normal file
60
components/Loading/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<!-- 上拉加载 -->
|
||||
<view>
|
||||
<view class="Loads acea-row row-center-wrapper" v-if="loading && !loaded" style="margin-top: .2rem;">
|
||||
<view v-if="loading">
|
||||
<view class="iconfont icon-jiazai loading acea-row row-center-wrapper"></view>
|
||||
<slot name="loading"></slot>
|
||||
正在加载中
|
||||
</view>
|
||||
<view v-else>
|
||||
<slot name="load"></slot>
|
||||
上拉加载更多
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Loading",
|
||||
props: {
|
||||
loaded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.Loads {
|
||||
height: 80upx;
|
||||
font-size: 25upx;
|
||||
color: #999;
|
||||
}
|
||||
.Loads .iconfont {
|
||||
font-size: 30upx;
|
||||
margin-right: 10upx;
|
||||
height: 32upx;
|
||||
line-height: 32upx;
|
||||
}
|
||||
/*加载动画*/
|
||||
@keyframes load {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.loadingpic {
|
||||
animation: load 3s linear 1s infinite;
|
||||
}
|
||||
.loading {
|
||||
animation: load linear 1s infinite;
|
||||
}
|
||||
</style>
|
||||
138
components/NavBar.vue
Normal file
138
components/NavBar.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<view class="navbar">
|
||||
<view class="content" :style="{ background: isScrolling ? '#fff' : bagColor,'padding-top': sysHeight + 'px' }">
|
||||
<view class="flex-center h-80 px-20">
|
||||
<view class="w-40 h-40 flex-center">
|
||||
<view
|
||||
v-show="showBack"
|
||||
@click="back"
|
||||
class="iconfont icon-ic_leftarrow"
|
||||
:style="{ color: `${iconColor}`, fontSize: `${iconSize}`, fontWeight: `${iconWeight}` }"
|
||||
></view>
|
||||
</view>
|
||||
<view class="flex-1 text-center"
|
||||
:style="{ color: `${textColor}`, fontSize: `${textSize}`, fontWeight: `${textWeight}` }"
|
||||
v-if="!custom">{{ titleText }}</view>
|
||||
<view class="flex-1" v-else>
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="w-40 h-40 flex-center">
|
||||
<view v-show="showRight" class="right-icon"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view :style="{'padding-top': sysHeight + 'px'}" v-if="showEmpty">
|
||||
<view class="h-80"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let sysHeight = uni.getWindowInfo().statusBarHeight;
|
||||
export default {
|
||||
name: 'navbar',
|
||||
props: {
|
||||
custom:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showEmpty:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动至下部
|
||||
isScrolling: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示返回icon
|
||||
showBack: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// Title
|
||||
titleText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// icon 颜色
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
},
|
||||
// icon 字号
|
||||
iconSize: {
|
||||
type: String,
|
||||
default: '40rpx'
|
||||
},
|
||||
// icon 字重
|
||||
iconWeight: {
|
||||
type: String,
|
||||
default: 'bold'
|
||||
},
|
||||
// Title 颜色
|
||||
textColor: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
// Title 字号
|
||||
textSize: {
|
||||
type: String,
|
||||
default: '34rpx'
|
||||
},
|
||||
// Title 字重
|
||||
textWeight: {
|
||||
type: String,
|
||||
default: '500'
|
||||
},
|
||||
// 背景色
|
||||
bagColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
showRight:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sysHeight:sysHeight,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
back() {
|
||||
let pages = getCurrentPages(); // 获取当前打开过的页面路由数,
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.navbar {
|
||||
position: relative;
|
||||
color: #333;
|
||||
.content {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 998;
|
||||
background-color: var(--view-theme);
|
||||
font-weight: 500;
|
||||
font-size: 34rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
218
components/WaterfallsFlow/WaterfallsFlow.vue
Normal file
218
components/WaterfallsFlow/WaterfallsFlow.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<view :class="'wf-page wf-page'+type">
|
||||
<!-- left -->
|
||||
<view>
|
||||
<view id="left" v-if="leftList.length">
|
||||
<view v-for="(item,index) in leftList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem
|
||||
:item="item"
|
||||
:isStore="isStore"
|
||||
:type="type"
|
||||
:isMerchant="isMerchant"
|
||||
:recommend="recommend"
|
||||
:showCart="showCart"/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- right -->
|
||||
<view>
|
||||
<view id="right" v-if="rightList.length">
|
||||
<view v-for="(item,index) in rightList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem
|
||||
:item="item"
|
||||
:isStore="isStore"
|
||||
:type="type"
|
||||
:isMerchant="isMerchant"
|
||||
:recommend="recommend"
|
||||
:showCart="showCart"/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WaterfallsFlowItem from './WaterfallsFlowItem.vue'
|
||||
export default {
|
||||
components: {
|
||||
WaterfallsFlowItem
|
||||
},
|
||||
props: {
|
||||
// 瀑布流列表
|
||||
wfList: {
|
||||
type: Array,
|
||||
require: true
|
||||
},
|
||||
updateNum: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isStore: {
|
||||
type: [String, Number],
|
||||
default: '1'
|
||||
},
|
||||
recommend:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showCart:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isMerchant:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allList: [], // 全部列表
|
||||
leftList: [], // 左边列表
|
||||
rightList: [], // 右边列表
|
||||
mark: 0, // 列表标记
|
||||
boxHeight: [], // 下标0和1分别为左列和右列高度
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 监听列表数据变化
|
||||
wfList: {
|
||||
handler(nVal,oVal){
|
||||
// 如果数据为空或新的列表数据少于旧的列表数据(通常为下拉刷新或切换排序或使用筛选器),初始化变量
|
||||
|
||||
if (!this.wfList.length ||
|
||||
(this.wfList.length === this.updateNum && this.wfList.length <= this.allList.length)) {
|
||||
this.allList = [];
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.mark = 0;
|
||||
}
|
||||
|
||||
// 如果列表有值,调用waterfall方法
|
||||
|
||||
if (this.wfList.length) {
|
||||
this.allList = this.wfList;
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.allList.forEach((v, i) => {
|
||||
if(this.allList.length < 3 || (this.allList.length <= 7 && this.allList.length - i > 1) || (this.allList.length > 7 && this.allList.length - i > 2)) {
|
||||
if(i % 2){
|
||||
this.rightList.push(v);
|
||||
}else{
|
||||
this.leftList.push(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
if(this.allList.length < 3){
|
||||
this.mark = this.allList.length+1;
|
||||
}else if(this.allList.length <= 7){
|
||||
this.mark = this.allList.length - 1;
|
||||
}else{
|
||||
this.mark = this.allList.length - 2;
|
||||
}
|
||||
if(this.mark < this.allList.length){
|
||||
this.waterFall()
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep:true
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
|
||||
// 监听标记,当标记发生变化,则执行下一个item排序
|
||||
mark() {
|
||||
const len = this.allList.length;
|
||||
if (this.mark < len && this.mark !== 0 && this.boxHeight.length) {
|
||||
this.waterFall();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 瀑布流排序
|
||||
waterFall() {
|
||||
const i = this.mark;
|
||||
if (i == 0) {
|
||||
// 初始化,从左边开始插入
|
||||
this.leftList.push(this.allList[i]);
|
||||
// 更新左边列表高度
|
||||
this.getViewHeight(0);
|
||||
} else if (i == 1) {
|
||||
// 第二个item插入,默认为右边插入
|
||||
this.rightList.push(this.allList[i]);
|
||||
// 更新右边列表高度
|
||||
this.getViewHeight(1);
|
||||
} else {
|
||||
// 根据左右列表高度判断下一个item应该插入哪边
|
||||
if(!this.boxHeight.length){
|
||||
this.rightList.length < this.leftList.length
|
||||
? this.rightList.push(this.allList[i])
|
||||
: this.leftList.push(this.allList[i]);
|
||||
} else {
|
||||
const leftOrRight = this.boxHeight[0] > this.boxHeight[1] ? 1 : 0;
|
||||
if (leftOrRight) {
|
||||
this.rightList.push(this.allList[i])
|
||||
} else {
|
||||
this.leftList.push(this.allList[i])
|
||||
}
|
||||
}
|
||||
// 更新插入列表高度
|
||||
this.getViewHeight();
|
||||
}
|
||||
},
|
||||
// 获取列表高度
|
||||
getViewHeight() {
|
||||
// 使用nextTick,确保页面更新结束后,再请求高度
|
||||
this.$nextTick(() => {
|
||||
setTimeout(()=>{
|
||||
uni.createSelectorQuery().in(this).select('#right').boundingClientRect(res => {
|
||||
res ? this.boxHeight[1] = res.height : '';
|
||||
uni.createSelectorQuery().in(this).select('#left').boundingClientRect(res => {
|
||||
res ? this.boxHeight[0] = res.height : '';
|
||||
this.mark = this.mark + 1;
|
||||
}).exec();
|
||||
}).exec();
|
||||
},100)
|
||||
})
|
||||
},
|
||||
// item点击
|
||||
itemTap(item) {
|
||||
this.$emit('itemTap', item)
|
||||
},
|
||||
// item点击
|
||||
|
||||
goShop(item) {
|
||||
this.$emit('goShop', item)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$page-padding: 10px;
|
||||
$grid-gap: 10px;
|
||||
|
||||
.wf-page {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: $grid-gap;
|
||||
}
|
||||
.wf-item {
|
||||
width: calc((100vw - 2 * #{$page-padding} - #{$grid-gap}) / 2);
|
||||
padding-bottom: $grid-gap;
|
||||
}
|
||||
.wf-page1 .wf-item{
|
||||
margin-top: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
129
components/WaterfallsFlow/WaterfallsFlowItem.vue
Normal file
129
components/WaterfallsFlow/WaterfallsFlowItem.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<view class="wf-item-page wf-page0">
|
||||
<view class='pictrue'>
|
||||
<easy-loadimage
|
||||
mode="widthFix"
|
||||
:image-src="item.image"
|
||||
:borderSrc="item.activity_frame.image"
|
||||
width="100%"
|
||||
borderRadius="16rpx 16rpx 0 0"></easy-loadimage>
|
||||
</view>
|
||||
<view class="info_box">
|
||||
<view class="w-full line2 fs-28 text--w111-333 lh-40rpx">
|
||||
<text v-if="item.brand_name" class="brand-tag">{{ item.brand_name }}</text>{{item.store_name}}
|
||||
</view>
|
||||
<view class="flex items-end flex-wrap mt-12 w-full" v-if="item.store_label.length && !isMerchant">
|
||||
<BaseTag
|
||||
:text="label.label_name"
|
||||
:color="label.color"
|
||||
:background="label.bg_color"
|
||||
:borderColor="label.border_color"
|
||||
:circle="label.border_color ? true : false"
|
||||
:imgSrc="label.icon"
|
||||
v-for="(label, idx) in item.store_label" :key="idx"></BaseTag>
|
||||
</view>
|
||||
<view class="flex-between-center mt-7" v-if="recommend">
|
||||
<view class="flex-y-center flex-wrap">
|
||||
<baseMoney :money="isVip ? item.vip_price : item.price" symbolSize="24" integerSize="40" decimalSize="24" weight></baseMoney>
|
||||
<view class="svip-label ml-8"
|
||||
v-if="Number(item.vip_price) > 0 && item.is_vip && !isVip">
|
||||
<text class="text">SVIP</text>
|
||||
<text class="px-8 SemiBold">¥{{item.vip_price}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="w-44 h-44 rd-24 bg-gradient flex-center" v-show="showCart">
|
||||
<text class="iconfont icon-ic_ShoppingCart1 text--w111-fff fs-26"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-8" v-else>
|
||||
<view class="flex-y-center flex-wrap mt-8">
|
||||
<baseMoney :money="item.price" symbolSize="24" integerSize="40" decimalSize="24" :color="isMerchant ? '#FF7E00' : 'var(--view-theme)'" weight></baseMoney>
|
||||
<view class="svip-label" v-if="Number(item.vip_price) > 0 && item.is_vip && !isMerchant">
|
||||
<text class="text">SVIP</text>
|
||||
<text class="px-8 SemiBold">¥{{item.vip_price}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-between-center mt-12">
|
||||
<text class="fs-22 text--w111-999">已售{{item.sales}}{{item.unit_name}}</text>
|
||||
<view class="w-44 h-44 rd-24 flex-center" :class="isMerchant ? 'bg-mer' : 'bg-gradient'" @tap.stop="addCartChange" v-show="showCart">
|
||||
<text class="iconfont icon-ic_ShoppingCart1 fs-26" :class="isMerchant ? 'text-mer' : 'text--w111-fff'"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import easyLoadimage from '@/components/easy-loadimage/easy-loadimage.vue'
|
||||
import {mapGetters} from "vuex";
|
||||
import {HTTP_REQUEST_URL} from '@/config/app';
|
||||
export default {
|
||||
components: {
|
||||
easyLoadimage
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
require: true
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
recommend:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showCart:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isMerchant:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domain: HTTP_REQUEST_URL,
|
||||
}
|
||||
},
|
||||
inject: {
|
||||
isVip:{
|
||||
from: 'isVip',
|
||||
default: false // 这里设置默认值为 false
|
||||
},
|
||||
addCartClick: {
|
||||
from: 'addCartClick',
|
||||
default: () => {}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addCartChange(){
|
||||
this.addCartClick(this.item);
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.wf-item-page {
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
.pictrue{
|
||||
max-height: 560rpx;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.info_box{
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: 0 0 20rpx 20rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
.bg-mer{
|
||||
background-color: $light-primary-merchant;
|
||||
}
|
||||
.text-mer{
|
||||
color: $primary-merchant;
|
||||
}
|
||||
</style>
|
||||
106
components/activityModal/index.vue
Normal file
106
components/activityModal/index.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<view>
|
||||
<uni-popup ref="popup" :mask-click="false">
|
||||
<view class="w-590 activity-popup">
|
||||
<view class="w-full h-800">
|
||||
<swiper class="w-full h-full"
|
||||
:circular="true"
|
||||
:indicator-dots="modalSwiper.length > 1 ? true : false"
|
||||
autoplay
|
||||
:interval="3000"
|
||||
:duration="500"
|
||||
indicator-color="rgba(255,255,255,0.5)"
|
||||
indicator-active-color="#ffffff">
|
||||
<swiper-item v-for="(item ,index) in modalSwiper" :key="index">
|
||||
<image :src="item.pic" mode="aspectFill" class="w-full h-full" @tap="pageJump(item)"></image>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
<view class="flex-x-center mt-54">
|
||||
<text class="iconfont icon-ic_close1 fs-60 text--w111-fff" @tap="closeModal"></text>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getActivityModalRecordApi } from "@/api/activity.js"
|
||||
export default {
|
||||
name:"activityModal",
|
||||
props:{
|
||||
pageIndex:{
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
pageId:{
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modalSwiper:[],
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
activityModalData(){
|
||||
return this.$store.state.app.activityModalList
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
pageIndex:{
|
||||
handler(val){
|
||||
setTimeout(()=>{
|
||||
if (!this.activityModalData.length) return;
|
||||
let dataList = this.activityModalData.filter(item => item.show_page.includes(val))
|
||||
.map(item => item.wechat_image).flat();
|
||||
let ids = this.activityModalData.filter(item => item.show_page.includes(val)).map(item => item.id);
|
||||
let closeModalSwiper = this.$Cache.get('closeModalSwiper');
|
||||
if (dataList.length && !closeModalSwiper) {
|
||||
this.modalSwiper = dataList;
|
||||
this.$refs.popup.open();
|
||||
this.recordLog(ids);
|
||||
}
|
||||
},500)
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
pageJump(item){
|
||||
this.$util.JumpPath(item.url);
|
||||
this.$refs.popup.close();
|
||||
},
|
||||
closeModal(){
|
||||
this.$refs.popup.close();
|
||||
},
|
||||
recordLog(ids){
|
||||
getActivityModalRecordApi({ids: ids.join()}).then(res=>{
|
||||
this.$Cache.set('closeModalSwiper', true);
|
||||
}).catch(err=>{
|
||||
console.log(err);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* #ifdef H5 || APP-PLUS */
|
||||
.activity-popup /deep/uni-swiper .uni-swiper-dot{
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
}
|
||||
/* #endif */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
.activity-popup /deep/.wx-swiper-dot~.wx-swiper-dot {
|
||||
width: 10rpx;
|
||||
height: 10rpx;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
</style>
|
||||
138
components/addressWindow/index.vue
Normal file
138
components/addressWindow/index.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<view>
|
||||
<base-drawer mode="bottom" :visible="address.address" background-color="transparent" mask maskClosable @close="close">
|
||||
<view class="w-full bg--w111-fff rd-t-40rpx py-32" v-if="shippingType == 0">
|
||||
<view class="text-center fs-32 text--w111-333 fw-500">选择地址</view>
|
||||
<scroll-view scroll-y="true" class="mt-64 px-32" style="max-height: 600rpx;">
|
||||
<view class="mb-38 flex-between-center"
|
||||
v-for="(item,index) in addressList" :key="index"
|
||||
:class="{'font-num': active==index}"
|
||||
@tap='tapAddress(index,item.id,item)'>
|
||||
<text class='iconfont icon-ic_location5 fs-36'></text>
|
||||
<view class="flex-1 pl-40">
|
||||
<view class="fs-28 fw-500">{{item.real_name}}<text class='phone pl-10'>{{item.phone}}</text></view>
|
||||
<view class="w-560 line1 mt-4">{{item.province}}{{item.city}}{{item.district}}{{item.street}}{{item.detail}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!is_loading && !addressList.length">
|
||||
<emptyPage title="暂无地址信息~" src="/statics/images/noAddress.png"></emptyPage>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="mx-20 pb-safe">
|
||||
<view class="mt-52 h-72 flex-center rd-36px bg-color fs-26 text--w111-fff" @tap='goAddressPages'>选择其它地址</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="w-full bg--w111-fff rd-t-40rpx py-32" v-else>
|
||||
<view class="text-center fs-32 text--w111-333 fw-500">选择自提点</view>
|
||||
<scroll-view scroll-y="true" class="mt-64 px-32" style="max-height: 600rpx;">
|
||||
<view class="mb-38 flex-between-center"
|
||||
v-for="(item,index) in storeList" :key="index"
|
||||
:class="{'font-num': active==index}"
|
||||
@tap='tapStore(index,item)'>
|
||||
<text class='iconfont icon-ic_location5 fs-36'></text>
|
||||
<view class="flex-1 pl-40">
|
||||
<view class="fs-28 fw-500">{{item.name}}<text class='phone pl-10'>{{item.phone}}</text></view>
|
||||
<view class="w-560 line1 mt-4">{{item.address}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</base-drawer>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getAddressList} from '@/api/user.js';
|
||||
import {HTTP_REQUEST_URL} from '@/config/app';
|
||||
import baseDrawer from '@/components/tui-drawer/tui-drawer.vue';
|
||||
import emptyPage from '@/components/emptyPage.vue';
|
||||
export default {
|
||||
props: {
|
||||
fromType: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
pagesUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
address: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {
|
||||
address: true,
|
||||
addressId: 0,
|
||||
};
|
||||
}
|
||||
},
|
||||
isLog: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
storeList:{
|
||||
type: Array,
|
||||
default: ()=> []
|
||||
},
|
||||
shippingType:{
|
||||
type: String | Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
components: {
|
||||
baseDrawer,
|
||||
emptyPage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: 0,
|
||||
//地址列表
|
||||
addressList: [],
|
||||
is_loading: true,
|
||||
imgHost: HTTP_REQUEST_URL
|
||||
};
|
||||
},
|
||||
watch:{
|
||||
shippingType(val){
|
||||
this.active = 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
tapAddress: function(e, addressid, row) {
|
||||
this.active = e;
|
||||
this.$emit('OnChangeAddress', addressid, row);
|
||||
},
|
||||
close: function() {
|
||||
this.$emit('changeClose');
|
||||
this.$emit('changeTextareaStatus');
|
||||
},
|
||||
goAddressPages: function() {
|
||||
this.$emit('changeClose');
|
||||
this.$emit('changeTextareaStatus');
|
||||
uni.navigateTo({
|
||||
url: this.pagesUrl
|
||||
});
|
||||
},
|
||||
tapStore(index, item){
|
||||
this.active = index;
|
||||
this.$emit('OnChangeAddress', item.id, item);
|
||||
},
|
||||
getAddressList: function() {
|
||||
let that = this;
|
||||
getAddressList({
|
||||
page: 1,
|
||||
limit: 5
|
||||
}).then(res => {
|
||||
let addressList = res.data;
|
||||
//处理默认选中项
|
||||
for (let i = 0, leng = addressList.length; i < leng; i++) {
|
||||
if (addressList[i].id == that.address.addressId) {
|
||||
that.active = i;
|
||||
}
|
||||
}
|
||||
that.$set(that, 'addressList', addressList);
|
||||
that.is_loading = false;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
267
components/areaWindow/index.vue
Normal file
267
components/areaWindow/index.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<!-- 地址下拉框 -->
|
||||
<view>
|
||||
<view class="address-window" :class="display==true?'on':''">
|
||||
<view class='title'>请选择所在地区<text class='iconfont icon-guanbi' @tap='close'></text></view>
|
||||
<view class="address-count">
|
||||
<view class="address-selected">
|
||||
<view v-for="(item,index) in selectedArr" :key="index" class="selected-list" :class="{active:index === selectedIndex}" @click="change(item.pid, index)">
|
||||
{{item.label}}
|
||||
<text class="iconfont icon-xiangyou"></text>
|
||||
</view>
|
||||
<view class="selected-list" :class="{active:-1 === selectedIndex}" v-if="showMore" @click="change(-1, -1)">
|
||||
<text class="iconfont icon-xiangyou"></text>
|
||||
请选择
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view scroll-y="true" :scroll-top="scrollTop" class="address-list" @scroll="scroll">
|
||||
<view v-for="(item,index) in addressList" :key="index" class="list" :class="{active:item.id === activeId}" @click="selected(item)">
|
||||
<text class="item-name">{{item.label}}</text>
|
||||
<text v-if="item.id === activeId" class="iconfont icon-duihao2"></text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
<view class='mask z-100' catchtouchmove="true" :hidden='display==false' @tap='close'></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
import { getCityData } from '@/api/api.js';
|
||||
|
||||
const CACHE_ADDRESS = {};
|
||||
|
||||
export default {
|
||||
props: {
|
||||
display: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
cityShow: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
address: {
|
||||
type:Array | Object,
|
||||
default:[]
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: 0,
|
||||
//地址列表
|
||||
addressList: [],
|
||||
selectedArr: [],
|
||||
selectedIndex: -1,
|
||||
is_loading: false,
|
||||
old: { scrollTop: 0 },
|
||||
scrollTop: 0
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
activeId(){
|
||||
return this.selectedIndex == -1 ? 0 : this.selectedArr[this.selectedIndex].id
|
||||
},
|
||||
showMore(){
|
||||
return this.selectedArr.length ? (this.selectedArr[this.selectedArr.length - 1].hasOwnProperty('children') && ((this.cityShow==1 && this.addressList.level<2) || (this.cityShow==2 && this.addressList.level<3) || (this.cityShow==3 && this.addressList.level<4))) : true
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
address(n){
|
||||
this.selectedArr = n ? [...n] : []
|
||||
},
|
||||
display(n){
|
||||
if(!n) {
|
||||
this.addressList = [];
|
||||
this.selectedArr = this.address ? [...this.address] : [];
|
||||
this.selectedIndex = -1;
|
||||
this.is_loading = false;
|
||||
}else{
|
||||
this.loadAddress(0)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadAddress(0)
|
||||
},
|
||||
methods: {
|
||||
change(pid,index){
|
||||
if(this.selectedIndex == index) return;
|
||||
if(pid === -1){
|
||||
pid = this.selectedArr.length ? this.selectedArr[this.selectedArr.length -1].id : 0;
|
||||
}
|
||||
this.selectedIndex = index;
|
||||
this.loadAddress(pid);
|
||||
},
|
||||
loadAddress(pid){
|
||||
if(CACHE_ADDRESS[pid]){
|
||||
this.addressList = CACHE_ADDRESS[pid];
|
||||
return ;
|
||||
}
|
||||
this.is_loading = true;
|
||||
getCityData(pid).then(res=>{
|
||||
this.is_loading = false;
|
||||
CACHE_ADDRESS[pid] = res.data;
|
||||
this.addressList = res.data;
|
||||
})
|
||||
this.goTop()
|
||||
},
|
||||
selected(item){
|
||||
if(this.is_loading) return;
|
||||
if(this.selectedIndex > -1){
|
||||
this.selectedArr.splice(this.selectedIndex + 1,999)
|
||||
this.selectedArr[this.selectedIndex] = item;
|
||||
this.selectedIndex = -1;
|
||||
}else if(!item.pid){
|
||||
this.selectedArr = [item];
|
||||
}else{
|
||||
this.selectedArr.push(item);
|
||||
}
|
||||
if(item.hasOwnProperty('children') && ((this.cityShow==1 && this.addressList[0].level<2) || (this.cityShow==2 && this.addressList[0].level<3) || (this.cityShow==3 && this.addressList[0].level<4))){
|
||||
this.loadAddress(item.id);
|
||||
} else {
|
||||
this.$emit('submit', [...this.selectedArr]);
|
||||
this.$emit('changeClose');
|
||||
}
|
||||
|
||||
this.goTop()
|
||||
},
|
||||
close: function() {
|
||||
this.$emit('changeClose');
|
||||
},
|
||||
scroll : function(e) {
|
||||
this.old.scrollTop = e.detail.scrollTop
|
||||
},
|
||||
goTop: function(e) {
|
||||
this.scrollTop = this.old.scrollTop
|
||||
this.$nextTick(() => {
|
||||
this.scrollTop = 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.address-window {
|
||||
background-color: #fff;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 101;
|
||||
border-radius: 30rpx 30rpx 0 0;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transition: all .3s cubic-bezier(.25, .5, .5, .9);
|
||||
}
|
||||
.address-window.on {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
.address-window .title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
height: 123rpx;
|
||||
line-height: 123rpx;
|
||||
position: relative;
|
||||
}
|
||||
.address-window .title .iconfont {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
color: #8a8a8a;
|
||||
font-size: 35rpx;
|
||||
}
|
||||
.address-count{
|
||||
.address-selected{
|
||||
padding: 0 30rpx;
|
||||
margin-top: 10rpx;
|
||||
position: relative;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 2rpx solid #f7f7f7;
|
||||
}
|
||||
.selected-list{
|
||||
font-size: 26rpx;
|
||||
color: #282828;
|
||||
line-height: 50rpx;
|
||||
padding-bottom: 10rpx;
|
||||
padding-left: 60rpx;
|
||||
position: relative;
|
||||
&.active{
|
||||
color: var(--view-theme);
|
||||
}
|
||||
&:before,&:after{
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
&:before{
|
||||
width: 4rpx;
|
||||
height: 100%;
|
||||
background-color: var(--view-theme);
|
||||
top: 0;
|
||||
left: 10rpx;
|
||||
}
|
||||
&:after{
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
background: var(--view-theme);
|
||||
border-radius: 100%;
|
||||
left: 6rpx;
|
||||
top: 50%;
|
||||
margin-top: -8rpx;
|
||||
}
|
||||
&:first-child,&:last-child{
|
||||
&:before{
|
||||
height: 50%;
|
||||
}
|
||||
}
|
||||
&:first-child{
|
||||
&:before{
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
.iconfont{
|
||||
font-size: 20rpx;
|
||||
float: right;
|
||||
color: #dddddd;
|
||||
}
|
||||
}
|
||||
scroll-view{
|
||||
height: 550rpx;
|
||||
}
|
||||
.address-list{
|
||||
padding: 0 30rpx;
|
||||
margin-top: 20rpx;
|
||||
box-sizing: border-box;
|
||||
.list{
|
||||
.iconfont{
|
||||
float: right;
|
||||
color: #ddd;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
.item-name{
|
||||
display: inline-block;
|
||||
line-height: 50rpx;
|
||||
margin-bottom: 20rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
&.active{
|
||||
color: var(--view-theme);
|
||||
.iconfont{
|
||||
color: var(--view-theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
3
components/authorize/index.vue
Normal file
3
components/authorize/index.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<view></view>
|
||||
</template>
|
||||
92
components/cartDiscount/index.vue
Normal file
92
components/cartDiscount/index.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 购物车优惠明细 -->
|
||||
<base-drawer mode="bottom" :visible="discountInfo.discount" background-color="transparent" mask maskClosable @close="closeDiscount">
|
||||
<view class="w-full bg--w111-fff rd-t-40rpx py-32">
|
||||
<view class="text-center fs-32 text--w111-333 fw-500">金额明细</view>
|
||||
<scroll-view scroll-y="true" class="mt-32 scroll-content">
|
||||
<view class="p-32">
|
||||
<view class="flex-between-center">
|
||||
<view class="fs-26">商品总价:</view>
|
||||
<view class="fs-28 SemiBold">¥{{discountInfo.deduction.sum_price}}</view>
|
||||
</view>
|
||||
<!-- <view class="flex-between-center mt-38">
|
||||
<view class="fs-26">优惠抵扣:</view>
|
||||
<view class="fs-28 SemiBold">-¥{{$util.$h.Sub(discountInfo.deduction.sum_price,discountInfo.deduction.pay_price)}}</view>
|
||||
</view> -->
|
||||
<view class="flex-between-center mt-38" v-if="discountInfo.deduction.coupon_price">
|
||||
<view class="fs-26">{{discountInfo.coupon.coupon_title}}:</view>
|
||||
<view class="fs-28 SemiBold">-¥{{discountInfo.deduction.coupon_price}}</view>
|
||||
</view>
|
||||
<view class="flex-between-center mt-38" v-if="discountInfo.deduction.first_order_price">
|
||||
<view class="fs-26">新人首单优惠:</view>
|
||||
<view class="fs-28 SemiBold">-¥{{discountInfo.deduction.first_order_price}}</view>
|
||||
</view>
|
||||
<view class="flex-between-center mt-38" v-if="discountInfo.deduction.promotions_price">
|
||||
<view class="fs-26">优惠活动:</view>
|
||||
<view class="fs-28 SemiBold">-¥{{discountInfo.deduction.promotions_price}}</view>
|
||||
</view>
|
||||
<view class="flex-between-center mt-38" v-if="discountInfo.deduction.vip_price">
|
||||
<view class="fs-26">会员优惠:</view>
|
||||
<view class="fs-28 SemiBold">-¥{{discountInfo.deduction.vip_price}}</view>
|
||||
</view>
|
||||
<view class="flex-between-center mt-38" v-if="discountInfo.deduction.channel_price">
|
||||
<view class="fs-26">采购优惠:</view>
|
||||
<view class="fs-28 SemiBold">-¥{{discountInfo.deduction.channel_price}}</view>
|
||||
</view>
|
||||
<view class="flex-between-center mt-44" v-if="discountInfo.deduction.vip_price">
|
||||
<view class="fs-28 fw-500">优惠合计</view>
|
||||
<view class="fs-32 SemiBold font-num">-¥{{$util.$h.Sub(discountInfo.deduction.sum_price,discountInfo.deduction.pay_price)}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view :class="showFooter ? 'show-footer' : 'hide-footer'"></view>
|
||||
</view>
|
||||
</base-drawer>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import baseDrawer from '@/components/tui-drawer/tui-drawer.vue';
|
||||
export default {
|
||||
props: {
|
||||
discountInfo: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
showFooter:{
|
||||
type:Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
components:{
|
||||
baseDrawer
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
closeDiscount(){
|
||||
this.$emit('myevent');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll-content {
|
||||
max-height: 800rpx;
|
||||
}
|
||||
.show-footer{
|
||||
height: calc(194rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.hide-footer{
|
||||
/* #ifdef H5 */
|
||||
height: calc(194rpx + env(safe-area-inset-bottom));
|
||||
/* #endif */
|
||||
/* #ifdef MP || APP-PLUS */
|
||||
height: calc(100rpx + env(safe-area-inset-bottom));
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
200
components/cartList/index.vue
Normal file
200
components/cartList/index.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 分类购物车下拉列表 -->
|
||||
<view class="cartList" :class="{'on':cartData.iScart,'show-pd': hideEmpty}" @touchmove.stop.prevent="moveHandle">
|
||||
<view class="title acea-row row-between-wrapper">
|
||||
<view class="name">购物车 <text class="fs-24 text--w111-999 pl-8">(共{{cartData.cartList.length}}件商品)</text> </view>
|
||||
<view class="del acea-row row-middle" @click="subDel">
|
||||
<view class="iconfont icon-ic_delete"></view>清空
|
||||
</view>
|
||||
</view>
|
||||
<view class="list pb-safe">
|
||||
<scroll-view class="scroll-list" scroll-y="true" :style="{
|
||||
'max-height': '800rpx',
|
||||
'margin-bottom': '126rpx'
|
||||
}">
|
||||
<view class="item flex" v-for="(item,index) in cartData.cartList" :key="index">
|
||||
<view class="pictrue">
|
||||
<image v-if="item.productInfo.attrInfo" :src='item.productInfo.attrInfo.image'></image>
|
||||
<image v-else :src='item.productInfo.image'></image>
|
||||
<view class="mantle" v-if="!item.status || !item.attrStatus"></view>
|
||||
</view>
|
||||
<view class="flex-1 flex-col justify-between ml-20">
|
||||
<view class="w-full">
|
||||
<view class="lh-40rpx fs-28 text--w111-333 line2"
|
||||
:class="(item.attrStatus && item.status)?'':'on'">{{item.productInfo.store_name}}</view>
|
||||
<view class="inline-block max-w-460 h-38 lh-38rpx mt-12 bg--w111-f5f5f5 text--w111-999 rd-20rpx px-12 text-center fs-22"
|
||||
v-if="item.productInfo.spec_type && item.attrStatus">
|
||||
<view class="flex">
|
||||
<text class="line1">属性: {{item.productInfo.attrInfo.suk}}</text>
|
||||
<text class="iconfont icon-ic_downarrow fs-24 ml-12"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="inline-block max-w-460 h-38 lh-38rpx mt-12 bg--w111-f5f5f5 text--w111-999 rd-20rpx px-12 text-center fs-22"
|
||||
v-else>
|
||||
<view class="flex">
|
||||
<text class="line1">属性: {{item.productInfo.attrInfo.suk}}</text>
|
||||
<text class="iconfont icon-ic_downarrow fs-24 ml-12"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-between-center mt-20">
|
||||
<baseMoney :money="item.truePrice" symbolSize="24" integerSize="40" decimalSize="24" weight></baseMoney>
|
||||
<view class="flex-y-center" v-if="item.attrStatus && item.status">
|
||||
<view class="flex-center w-48 h-48 rd-30rpx bg--w111-f5f5f5 text--w111-333"
|
||||
:class="{'disabled-btn': item.productInfo.min_qty && item.cart_num == item.productInfo.min_qty}"
|
||||
@click="leaveCart(index, item)">
|
||||
<text class="iconfont icon-ic_Reduce fs-32"></text>
|
||||
</view>
|
||||
<view class="fs-30 text--w111-333 px-20">{{item.cart_num}}</view>
|
||||
<view class="flex-center w-48 h-48 rd-30rpx bg-color text--w111-fff" @click="joinCart(index)">
|
||||
<text class="iconfont icon-ic_increase fs-32"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="noBnt" v-else-if="!item.attrStatus">已售罄</view>
|
||||
<view class="noBnt" v-else-if="!item.status">已下架</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view :class="isFooter ? 'show-footer' : ''"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mask" v-if="cartData.iScart" @click="closeList" @touchmove.stop.prevent="moveHandle"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props:{
|
||||
cartData: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
isFooter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideEmpty:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
moveHandle(){
|
||||
return false
|
||||
},
|
||||
closeList(){
|
||||
this.$emit('closeList', false);
|
||||
},
|
||||
leaveCart(index,item){
|
||||
if(item.productInfo.min_qty && item.cart_num == item.productInfo.min_qty) return
|
||||
this.$emit('ChangeCartNumDan', false,index);
|
||||
},
|
||||
joinCart(index){
|
||||
this.$emit('ChangeCartNumDan', true,index);
|
||||
},
|
||||
subDel(){
|
||||
this.$emit('ChangeSubDel');
|
||||
},
|
||||
oneDel(id,index){
|
||||
this.$emit('ChangeOneDel',id,index);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.mask{
|
||||
z-index: 99;
|
||||
}
|
||||
.cartList{
|
||||
position: fixed;
|
||||
left:0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
z-index:100;
|
||||
padding: 40rpx 32rpx 0;
|
||||
box-sizing: border-box;
|
||||
border-radius:40rpx 40rpx 0 0;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transition: all .3s cubic-bezier(.25, .5, .5, .9);
|
||||
&.on{
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
.title{
|
||||
margin-bottom: 32rpx;
|
||||
.name{
|
||||
font-size:32rpx;
|
||||
color: #333;
|
||||
font-weight:500;
|
||||
}
|
||||
.del{
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
.iconfont{
|
||||
margin-right: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.list{
|
||||
max-height: 1000rpx;
|
||||
.item{
|
||||
margin-bottom: 32rpx;
|
||||
.pictrue{
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 16rpx;
|
||||
position: relative;
|
||||
image{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
.mantle{
|
||||
position: absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:rgba(255,255,255,0.65);
|
||||
border-radius:16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.show-pd{
|
||||
/* #ifdef H5 */
|
||||
padding-bottom: calc(100rpx + constant(safe-area-inset-bottom)); ///兼容 IOS<11.2/
|
||||
padding-bottom: calc(100rpx + env(safe-area-inset-bottom)); ///兼容 IOS>11.2/
|
||||
/* #endif */
|
||||
}
|
||||
.noBnt{
|
||||
width:126rpx;
|
||||
height:44rpx;
|
||||
background:#f5f5f5;
|
||||
border-radius:22rpx;
|
||||
text-align: center;
|
||||
line-height: 44rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
.max-w-460{
|
||||
max-width: 460rpx;;
|
||||
}
|
||||
.show-footer{
|
||||
/* #ifdef MP || APP-PLUS */
|
||||
height: calc(104rpx + env(safe-area-inset-bottom));
|
||||
/* #endif */
|
||||
|
||||
}
|
||||
.disabled-btn{
|
||||
color: #DEDEDE;
|
||||
}
|
||||
</style>
|
||||
148
components/countDown/index.vue
Normal file
148
components/countDown/index.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<view class="time" :style="justifyLeft">
|
||||
<!-- 倒计时 -->
|
||||
<text class="title red" v-if="tipText.trim()">{{ tipText }}</text>
|
||||
<text class="styleAll" :style="'background:' + bgColor + ';color:' + colors + ';'" v-if="isDay === true">
|
||||
{{ day }}
|
||||
<text class="dayTxt">{{ inDayText }}</text>
|
||||
</text>
|
||||
<text class="timeTxt red" :style="'color:' + dotColor" v-if="dayText">{{ dayText }}</text>
|
||||
<text class="styleAll" :style="'background:' + bgColor + ';color:' + colors + ';'">{{ hour }}</text>
|
||||
<text class="timeTxt red" :style="'color:' + dotColor" v-if="hourText">{{ hourText }}</text>
|
||||
<text class="styleAll" :style="'background:' + bgColor + ';color:' + colors + ';'">{{ minute }}</text>
|
||||
<text class="timeTxt red" :style="'color:' + dotColor" v-if="minuteText">{{ minuteText }}</text>
|
||||
<text class="styleAll" :style="'background:' + bgColor + ';color:' + colors + ';'" v-if="isSecond === true">{{ second }}</text>
|
||||
<text class="timeTxt red" :style="'color:' + dotColor" v-if="secondText">{{ secondText }}</text>
|
||||
<slot name="bottom"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'countDown',
|
||||
props: {
|
||||
justifyLeft: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//距离开始提示文字
|
||||
tipText: {
|
||||
type: String,
|
||||
default: '倒计时'
|
||||
},
|
||||
inDayText: {
|
||||
type: String,
|
||||
default: '天'
|
||||
},
|
||||
dayText: {
|
||||
type: String,
|
||||
default: '天'
|
||||
},
|
||||
hourText: {
|
||||
type: String,
|
||||
default: '时'
|
||||
},
|
||||
minuteText: {
|
||||
type: String,
|
||||
default: '分'
|
||||
},
|
||||
secondText: {
|
||||
type: String,
|
||||
default: '秒'
|
||||
},
|
||||
datatime: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isDay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isSecond: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
colors: {
|
||||
type: String,
|
||||
default: '#e93323'
|
||||
},
|
||||
dotColor: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
day: '00',
|
||||
hour: '00',
|
||||
minute: '00',
|
||||
second: '00',
|
||||
times: null
|
||||
};
|
||||
},
|
||||
watch:{
|
||||
datatime:{
|
||||
handler(val){
|
||||
this.show_time();
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show_time: function () {
|
||||
let that = this;
|
||||
|
||||
function runTime() {
|
||||
//时间函数
|
||||
let intDiff = that.datatime - Date.parse(new Date()) / 1000; //获取数据中的时间戳的时间差;
|
||||
let day = 0,
|
||||
hour = 0,
|
||||
minute = 0,
|
||||
second = 0;
|
||||
if (intDiff > 0) {
|
||||
//转换时间
|
||||
if (that.isDay === true) {
|
||||
day = Math.floor(intDiff / (60 * 60 * 24));
|
||||
} else {
|
||||
day = 0;
|
||||
}
|
||||
hour = Math.floor(intDiff / (60 * 60)) - day * 24;
|
||||
minute = Math.floor(intDiff / 60) - day * 24 * 60 - hour * 60;
|
||||
second = Math.floor(intDiff) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
|
||||
if (hour <= 9) hour = '0' + hour;
|
||||
if (minute <= 9) minute = '0' + minute;
|
||||
if (second <= 9) second = '0' + second;
|
||||
that.day = day;
|
||||
that.hour = hour;
|
||||
that.minute = minute;
|
||||
that.second = second;
|
||||
} else {
|
||||
that.day = '00';
|
||||
that.hour = '00';
|
||||
that.minute = '00';
|
||||
that.second = '00';
|
||||
clearInterval(that.times);
|
||||
that.times = null;
|
||||
that.$emit('endTime', 1);
|
||||
}
|
||||
}
|
||||
runTime();
|
||||
this.times = setInterval(runTime, 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.time {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.timeTxt {
|
||||
margin: 0 4rpx;
|
||||
}
|
||||
</style>
|
||||
219
components/couponListWindow/index.vue
Normal file
219
components/couponListWindow/index.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<view>
|
||||
<base-drawer mode="bottom" :visible="coupon.coupon" background-color="transparent" mask maskClosable @close="close">
|
||||
<view class="w-full bg--w111-f5f5f5 rd-t-40rpx">
|
||||
<view class="flex-around-center fs-30 nav" v-if="coupon.count">
|
||||
<view v-if="coupon.count[0]" class="h-100 flex-y-center" :class="coupon.type === 0 ? 'active' : ''" @tap="setType(0)">通用券</view>
|
||||
<view v-if="coupon.count[1]" class="h-100 flex-y-center" :class="coupon.type === 1 ? 'active' : ''" @tap="setType(1)">品类券</view>
|
||||
<view v-if="coupon.count[2]" class="h-100 flex-y-center" :class="coupon.type === 2 ? 'active' : ''" @tap="setType(2)">商品券</view>
|
||||
<view v-if="coupon.count[3]" class="h-100 flex-y-center" :class="coupon.type === 3 ? 'active' : ''" @tap="setType(3)">品牌券</view>
|
||||
</view>
|
||||
<view class="text-center fs-32 text--w111-333 fw-500 pt-32 relative" v-else>
|
||||
优惠券
|
||||
<view class="w-36 h-36 rd-50-p111- flex-center close-btn" @tap="close">
|
||||
<text class="iconfont icon-ic_close fs-24 text--w111-666"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="px-20 scroll-content">
|
||||
<scroll-view scroll-y="true" style="max-height: 800rpx">
|
||||
<view class="coupon-list" v-if="coupon.list.length">
|
||||
<view v-for="(item, index) in coupon.list" :key="index" @tap="getCouponUser(index, item.id)">
|
||||
<view class="mt-24 h-170 bg--w111-fff text-center rd-16rpx flex">
|
||||
<view class="left-bg bg-gradient">
|
||||
<view class="left-container w-full h-full relative flex-col flex-between-center py-32">
|
||||
<baseMoney :money="item.coupon_price" symbolSize="28" integerSize="52" decimalSize="28" color="#ffffff" isCoupon v-if="item.coupon_type == 1"></baseMoney>
|
||||
<view v-else class="text--w111-fff fs-52">
|
||||
{{ parseFloat(item.coupon_price) / 10 }}
|
||||
<text class="fs-28">折</text>
|
||||
</view>
|
||||
<text class="fs-24 text--w111-fff" v-show="item.use_min_price == 0">无门槛券</text>
|
||||
<text class="fs-24 text--w111-fff" v-show="item.use_min_price != 0">满{{ item.use_min_price }}元可用</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="right-box pt-24 pl-24 pr-14 pb-22 flex-1 flex-col justify-between bg--w111-fff">
|
||||
<view class="flex-y-center">
|
||||
<view class="fs-28 fw-500 text--w111-333 lh-40rpx">{{ item.title }}</view>
|
||||
</view>
|
||||
<view class="flex-between-center">
|
||||
<view class="fs-20 text--w111-666 lh-28rpx" v-if="item.coupon_time">领取后{{ item.coupon_time }}天内可用</view>
|
||||
<view class="fs-20 text--w111-666 lh-28rpx" v-else>{{ item.start_time ? item.start_time + '-' : '' }}{{ item.end_time }}</view>
|
||||
<view class="con_btn bg-primary-light text-primary-con w-136 h-52 rd-28rpx fs-22 flex-center" v-if="coupon.count">
|
||||
{{ item.is_use ? '去使用' : '立即领取' }}
|
||||
</view>
|
||||
<view class="iconfont fs-36 pr-14" :class="" v-else>
|
||||
<view class="iconfont icon-a-ic_CompleteSelect font-color" :class="item.receive_type === 4 ? 'svip' : 'font-num'" v-if="item.is_use"></view>
|
||||
<view class="iconfont icon-ic_unselect text--w111-ccc" v-else></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-y-center fs-20 text--w111-999 lh-28rpx mt-20">
|
||||
<text>{{ item.type | typeFilter }}</text>
|
||||
<view v-show="item.rule" @tap.stop="toggleRule(index)">
|
||||
<text class="pl-8 fs-20">| 查看用券规则</text>
|
||||
<text class="iconfont icon-ic_downarrow fs-20 ml-4"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rule-desc" v-show="item.ruleshow" v-html="item.rule"></view>
|
||||
</view>
|
||||
<view class="h-200"></view>
|
||||
</view>
|
||||
<view class="mt-20" v-else>
|
||||
<emptyPage title="暂无优惠券" src="/statics/images/noCoupon.gif"></emptyPage>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</base-drawer>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { setCouponReceive } from '@/api/api.js';
|
||||
import { HTTP_REQUEST_URL } from '@/config/app';
|
||||
import baseDrawer from '@/components/tui-drawer/tui-drawer.vue';
|
||||
import emptyPage from '@/components/emptyPage.vue';
|
||||
export default {
|
||||
filters: {
|
||||
typeFilter(val) {
|
||||
let obj = {
|
||||
0: '通用券',
|
||||
1: '品类券',
|
||||
2: '商品券',
|
||||
3: '品牌券'
|
||||
};
|
||||
return obj[val];
|
||||
}
|
||||
},
|
||||
props: {
|
||||
//打开状态 0=领取优惠券,1=使用优惠券
|
||||
openType: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
coupon: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
baseDrawer,
|
||||
emptyPage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 0,
|
||||
imgHost: HTTP_REQUEST_URL
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
close: function () {
|
||||
this.$emit('ChangCouponsClone');
|
||||
this.type = 0;
|
||||
},
|
||||
getCouponUser: function (index, id) {
|
||||
let that = this;
|
||||
let list = that.coupon.list;
|
||||
if (list[index].is_use == true && this.openType == 0) {
|
||||
that.$emit('ChangCoupons', 0);
|
||||
return;
|
||||
}
|
||||
switch (this.openType) {
|
||||
case 0:
|
||||
//领取优惠券
|
||||
setCouponReceive(id)
|
||||
.then((res) => {
|
||||
that.$emit('ChangCouponsUseState', index);
|
||||
that.$util.Tips({
|
||||
title: '领取成功'
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.showToast({
|
||||
title: err,
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
that.$emit('ChangCoupons', index);
|
||||
break;
|
||||
}
|
||||
},
|
||||
setType: function (type) {
|
||||
this.type = type;
|
||||
this.$emit('tabCouponType', type);
|
||||
},
|
||||
toggleRule(index) {
|
||||
this.$emit('ruleToggle', index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.nav {
|
||||
height: 100rpx;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.active {
|
||||
border-bottom: 1px solid var(--view-theme);
|
||||
color: var(--view-theme);
|
||||
}
|
||||
.left-bg {
|
||||
width: 190rpx;
|
||||
height: 170rpx;
|
||||
background-size: 100%;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 24rpx 0 0 24rpx;
|
||||
}
|
||||
.right-box {
|
||||
border-radius: 0 24rpx 24rpx 0;
|
||||
}
|
||||
.left-container {
|
||||
background: radial-gradient(circle at 0px 84rpx, #f5f5f5 12rpx, transparent 0px) top;
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0px;
|
||||
width: 6rpx;
|
||||
height: 100%;
|
||||
background-image: radial-gradient(circle at 6rpx 12rpx, #ffffff 6rpx, transparent 6rpx);
|
||||
background-size: 6rpx 18rpx;
|
||||
}
|
||||
}
|
||||
.text-primary-con {
|
||||
color: var(--view-theme);
|
||||
}
|
||||
.bg-primary-light {
|
||||
background: var(--view-minorColorT);
|
||||
}
|
||||
.con-border {
|
||||
border: 1px solid var(--view-theme);
|
||||
}
|
||||
.scroll-content {
|
||||
height: 800rpx;
|
||||
.coupon-list{
|
||||
margin-bottom: 120rpx;
|
||||
}
|
||||
}
|
||||
.rule-desc {
|
||||
margin-top: -16rpx;
|
||||
padding: 40rpx 24rpx 24rpx;
|
||||
white-space: pre-wrap;
|
||||
font-size: 20rpx;
|
||||
line-height: 28rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 0 0 16rpx 16rpx;
|
||||
color: #999;
|
||||
}
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
right: 32rpx;
|
||||
top: 36rpx;
|
||||
background-color: #eee;
|
||||
}
|
||||
</style>
|
||||
179
components/couponWindow/index.vue
Normal file
179
components/couponWindow/index.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 优惠券弹窗 -->
|
||||
<view class="coupon-window relative" :class="{ on: window}">
|
||||
<view class="box-1 cover relative" :style="[box1]">
|
||||
<view class="box-2 bg--w111-fff relative px-32">
|
||||
<view class="box-ht cover abs-lt" :style="[boxHt]"></view>
|
||||
<view class="flex-center mt-26 fs-36 lh-60rpx fw-500 relative z-20">
|
||||
恭喜获得<text class="red">{{couponList.length}}张</text>优惠券
|
||||
</view>
|
||||
<view class="box-item mt-16 flex-between-center relative z-20"
|
||||
v-for="(item,index) in couponList" :key="index"
|
||||
:style="[boxItem]">
|
||||
<baseMoney
|
||||
:money="item.coupon_price"
|
||||
symbolSize="32"
|
||||
integerSize="52"
|
||||
decimalSize="32"
|
||||
color="#e93323"
|
||||
isCoupon
|
||||
v-if="item.coupon_type==1"></baseMoney>
|
||||
<view v-else class="font-color SemiBold fs-42 SemiBold">{{parseFloat(item.coupon_price)/10}}折</view>
|
||||
<view>
|
||||
<view class="fs-28 lh-40rpx red w-234 line1">{{item.coupon_title}}</view>
|
||||
<view class="fs-20 lh-28rpx text--w111-666 mt-8" v-if="item.coupon_time">领取后{{item.coupon_time}}天内可用</view>
|
||||
<view class="fs-20 lh-28rpx text--w111-666 mt-8" v-else>{{item.start_time ? item.start_time+'-' : ''}}{{item.end_time === 0 ? '不限时': item.end_time}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="box-3 cover abs-lb" :style="[box3]">
|
||||
<view class="btn-box flex-center fs-34 fw-500" @tap="goPage">立即领取</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="iconfont icon-ic_close1 fs-60 text--w111-fff" @tap="close"></text>
|
||||
</view>
|
||||
<view class='mask' catchtouchmove="true" :hidden="window==false" @touchmove.stop.prevent></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {HTTP_REQUEST_URL} from '@/config/app';
|
||||
export default {
|
||||
props: {
|
||||
window: {
|
||||
type: Boolean | String | Number,
|
||||
default: false,
|
||||
},
|
||||
couponList: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
couponImage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imgHost:HTTP_REQUEST_URL
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
boxHt(){
|
||||
return {
|
||||
backgroundImage: 'url('+ HTTP_REQUEST_URL +'/statics/images/product/new_coupon_bg1.png'+')'
|
||||
}
|
||||
},
|
||||
boxItem(){
|
||||
return {
|
||||
backgroundImage: 'url('+ HTTP_REQUEST_URL +'/statics/images/product/new_coupon_bg2.png'+')'
|
||||
}
|
||||
},
|
||||
box3(){
|
||||
return {
|
||||
backgroundImage: 'url('+ HTTP_REQUEST_URL +'/statics/images/product/new_coupon_bg3.png'+')'
|
||||
}
|
||||
},
|
||||
box1(){
|
||||
return {
|
||||
backgroundImage: 'url('+ HTTP_REQUEST_URL +'/statics/images/product/new_coupon_bg4.png'+')'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goPage(){
|
||||
this.$emit('onColse');
|
||||
uni.navigateTo({
|
||||
url: '/pages/activity/coupon/index'
|
||||
})
|
||||
},
|
||||
close:function(){
|
||||
this.$emit('onColse');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mask {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.coupon-window {
|
||||
width: 574rpx;
|
||||
height: 860rpx;
|
||||
position: fixed;
|
||||
top: 20%;
|
||||
z-index: 10000;
|
||||
left: 50%;
|
||||
margin-left: -286rpx;
|
||||
transform: translate3d(0, -200%, 0);
|
||||
transition: all .3s cubic-bezier(.25, .5, .5, .9);
|
||||
border-radius: 30rpx 30rpx 0 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.coupon-window.on {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
.box-1{
|
||||
width: 100%;
|
||||
height: 462rpx;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 108rpx;
|
||||
}
|
||||
.box-2{
|
||||
width: 524rpx;
|
||||
max-height: 508rpx;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 188rpx;
|
||||
border-radius: 40rpx 40rpx 0 0;
|
||||
overflow-y: auto;
|
||||
border-radius: 24px;
|
||||
}
|
||||
.box-3{
|
||||
width: 100%;
|
||||
height: 242rpx;
|
||||
}
|
||||
.box-ht{
|
||||
width: 524rpx;
|
||||
height: 136rpx;
|
||||
}
|
||||
.box-item{
|
||||
width: 100%;
|
||||
height: 140rpx;
|
||||
background-size: 100%;
|
||||
padding: 0 40rpx 0 20rpx;
|
||||
}
|
||||
.btn-box{
|
||||
width: 460rpx;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(90deg, #FFD10C 0%, #FEEF4C 100%);
|
||||
border-radius: 44rpx;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 48rpx;
|
||||
}
|
||||
.cover{
|
||||
background-size: cover;
|
||||
}
|
||||
.icon-ic_close1{
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 0;
|
||||
}
|
||||
.red{
|
||||
color: #e93323;
|
||||
}
|
||||
.SemiBold{
|
||||
font-family:'SemiBold';
|
||||
}
|
||||
</style>
|
||||
145
components/cusPreviewImg/index.vue
Normal file
145
components/cusPreviewImg/index.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<!-- 属性规格放大图轮播 -->
|
||||
<view class="previewImg" v-if="showBox" @touchmove.stop.prevent>
|
||||
<view class="mask" @click="close">
|
||||
<swiper @change="changeSwiper" class="mask-swiper" :current="currentIndex" :circular="circular" :duration="duration">
|
||||
<swiper-item v-for="(src, i) in list" :key="i" class="flex flex-column justify-center align-center">
|
||||
<image class="mask-swiper-img" :src="src.image" mode="widthFix" />
|
||||
<view class="mask_sku">
|
||||
<text class="sku_name">{{src.suk}}</text>
|
||||
<text class="sku_price fs-36 fw-600"><text v-if="src.integral">{{src.integral}} 积分 +</text> <text class="money-fix"></text>{{price || src.price}} 元</text>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
<view class="pagebox" v-if="list.length>0">{{ Number(currentIndex) + 1 }} / {{ list.length }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'cus-previewImg',
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
price:{
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
circular: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 500
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0,
|
||||
showBox: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 左右切换
|
||||
changeSwiper(e) {
|
||||
this.currentIndex = e.target.current;
|
||||
this.$emit('changeSwitch',e.target.current)
|
||||
},
|
||||
open(current) {
|
||||
if (!current || !this.list.length) return;
|
||||
this.currentIndex = this.list.map((item)=>item.suk).indexOf(current);
|
||||
this.showBox = true;
|
||||
},
|
||||
close() {
|
||||
this.showBox = false;
|
||||
}
|
||||
// shareFriend(){
|
||||
// this.$emit('shareFriend')
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@mixin full {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.previewImg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 4100;
|
||||
@include full;
|
||||
.mask {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #000;
|
||||
opacity: 1;
|
||||
z-index: 8;
|
||||
@include full;
|
||||
&-swiper {
|
||||
@include full;
|
||||
&-img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.pagebox{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 164rpx;
|
||||
z-index: 300;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.mask_sku{
|
||||
color: #fff;
|
||||
max-width: 80%;
|
||||
z-index: 300;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 30rpx;
|
||||
.sku_name{
|
||||
font-size: 12px;
|
||||
border: 1px solid #fff;
|
||||
padding: 10rpx 30rpx 10rpx;
|
||||
border-radius: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.sku_price{
|
||||
padding-top: 10px;
|
||||
}
|
||||
.money-fix{
|
||||
font-size: 20rpx;
|
||||
font-weight: normal;
|
||||
padding-right: 4rpx;
|
||||
}
|
||||
}
|
||||
.font12{
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.share_btn{
|
||||
position: absolute;
|
||||
top:70rpx;
|
||||
right:50rpx;
|
||||
font-size: 40rpx;
|
||||
color:#fff;
|
||||
z-index: 300;
|
||||
}
|
||||
.flex-column{flex-direction: column;}
|
||||
.justify-center {justify-content: center;}
|
||||
.align-center {align-items: center;}
|
||||
</style>
|
||||
98
components/customForm/index.vue
Normal file
98
components/customForm/index.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<!-- 自定义组件 -->
|
||||
<view class='wrapper card' v-if="customForm && customForm.length && isShow">
|
||||
<view class='item acea-row row-between' v-for="(item,index) in customForm" :key="index" v-if="(item.value && ['uploadPicture','dateranges'].indexOf(item.name) == -1) || (item.value.length && ['uploadPicture','dateranges'].indexOf(item.name) != -1)">
|
||||
<view class="title">{{item.titleConfig.value}}:</view>
|
||||
<view v-if="item.name == 'uploadPicture'" class='conter'>
|
||||
<view class='pictrue' v-for="(img,indexn) in item.value" :key="indexn">
|
||||
<image :src='img' mode="aspectFill" @click='getCustomForm(index,indexn)'></image>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="item.name == 'dateranges'" class="conter">
|
||||
<text v-if="item.value.length">{{item.value[0]+'/'+item.value[1]}}</text>
|
||||
</view>
|
||||
<view v-else class='conter'>{{item.value}}</view>
|
||||
</view>
|
||||
<slot name="bottom"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'customForm',
|
||||
props: {
|
||||
customForm:{
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow:0
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
customForm (value) {
|
||||
if(value && value.length){
|
||||
value.forEach((item)=>{
|
||||
if(item.value){
|
||||
return this.isShow = 1
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
getCustomForm: function(index,indexn) {
|
||||
uni.previewImage({
|
||||
urls: this.customForm[index].value,
|
||||
current: this.customForm[index].value[indexn]
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.wrapper{
|
||||
background-color: #fff;
|
||||
margin-top: 6px;
|
||||
padding: 15px;
|
||||
}
|
||||
.wrapper .item {
|
||||
font-size: 28rpx;
|
||||
color: #282828;
|
||||
}
|
||||
|
||||
.wrapper .item .title{
|
||||
width: 200rpx;
|
||||
}
|
||||
|
||||
.wrapper .item~.item {
|
||||
margin-top: 20rpx;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.wrapper .item .conter {
|
||||
color: #868686;
|
||||
width: 460rpx;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-end;
|
||||
text-align: right;
|
||||
.pictrue{
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-left: 6rpx;
|
||||
image{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
732
components/discoverVideo/index.vue
Normal file
732
components/discoverVideo/index.vue
Normal file
@@ -0,0 +1,732 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="fixed-lt pl-20 z-4000 h-80 flex-y-center"
|
||||
:style="{top: sysHeight + 'px'}" v-if="fullVideo" @tap="backPage">
|
||||
<text class="iconfont icon-ic_leftarrow fs-40 text--w111-fff"></text>
|
||||
</view>
|
||||
<view>
|
||||
<swiper class="video-swiper"
|
||||
vertical
|
||||
:circular="false"
|
||||
:autoplay="false"
|
||||
:duration="300"
|
||||
:current="swiperCurrent"
|
||||
@change="videoChange">
|
||||
<swiper-item v-for="(item, index) in swiperData" :key="index">
|
||||
<view class="swiper-video-item h-full">
|
||||
<view class="h-full relative" v-if="item.content_type == 2">
|
||||
<video
|
||||
:id="item.id + 'k' + index"
|
||||
:muted="false"
|
||||
:autoplay="index === swiperCurrent"
|
||||
:loop="true"
|
||||
:controls="false"
|
||||
:http-cache="true"
|
||||
:page-gesture="false"
|
||||
:show-fullscreen-btn="false"
|
||||
:show-loading="false"
|
||||
:show-center-play-btn="false"
|
||||
:enable-progress-gesture="false"
|
||||
:src="item.video_url"
|
||||
@ended="ended"
|
||||
@click="tapVideoHover($event)"
|
||||
class="w-full h-full"
|
||||
></video>
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="abs-center w-full h-full flex-center" @tap="playVideo" v-if="!isPlay && Auth.isWeixin()">
|
||||
<image src="@/static/images/stop.png" mode="" class="w-160 h-160"></image>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="flex-y-center h-full" v-else>
|
||||
<swiper
|
||||
class="w-full h-full"
|
||||
circular
|
||||
:autoplay="index === swiperCurrent"
|
||||
:current="imgCurrent"
|
||||
:interval="4000"
|
||||
:duration="500"
|
||||
@change="photoChange">
|
||||
<swiper-item v-for="(photo, i) in item.slider_image" :key="i">
|
||||
<view class="h-full flex-y-center">
|
||||
<image :src="photo" mode="widthFix" class="w-full"></image>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
<view class="right-action text--w111-fff flex-col flex-y-center z-800"
|
||||
:class="{'action-box':showFooter,'full-action': fullVideo}">
|
||||
<view class="relative">
|
||||
<view class="w-88 h-88 rd-50-p111- avatar-box" v-if="currentData && currentData.author_image" >
|
||||
<image :src="currentData.author_image" class="w-full h-full rd-50-p111-" @tap="toUser()"></image>
|
||||
</view>
|
||||
<view class="w-88 h-88 rd-50-p111- avatar-box" v-else></view>
|
||||
<view class="follow-icon flex-center" v-if="!currentData.is_follow && !currentData.is_self"
|
||||
@tap="followFun">
|
||||
<text class="iconfont icon-ic_increase fs-20 text--w111-fff"></text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 2.点赞 -->
|
||||
<view class="mt-50 flex-col flex-y-center" @tap="contentLike">
|
||||
<text class="iconfont fs-72 icon-ic_love_2 shadow" :class="{'like-heart': currentData.is_like}"></text>
|
||||
<text class="fs-22 pt-10 text-center shadow">{{currentData.like_num > 0 ? currentData.like_num : '点赞'}}</text>
|
||||
</view>
|
||||
<!-- 3.评论 -->
|
||||
<view class="mt-36 flex-col flex-y-center" @tap="toggleReplyDrawer" v-if="replyStatus">
|
||||
<text class="iconfont icon-icon_comment fs-64 shadow"></text>
|
||||
<text class="fs-22 pt-16 text-center shadow">{{currentData.comment_num > 0 ? currentData.comment_num : '评论'}}</text>
|
||||
</view>
|
||||
<!-- 3.分享 -->
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<view class="mt-36 flex-col flex-y-center" @tap="openShare">
|
||||
<text class="iconfont icon-icon_transmit fs-66 shadow"></text>
|
||||
<text class="fs-22 pt-10 shadow">分享</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="mt-36 flex-col flex-y-center bg-transparent text--w111-fff" open-type="share" hover-class="none" @tap="openShare">
|
||||
<text class="iconfont icon-icon_transmit fs-66 shadow"></text>
|
||||
<text class="fs-22 pt-10 shadow">分享</text>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<view class="mt-36 flex-col flex-y-center relative" v-if="showMore">
|
||||
<text class="iconfont icon-ic_more fs-60 shadow" @tap="()=>{showBubble = !showBubble}"></text>
|
||||
<view class="bubble_box">
|
||||
<view class="bubble text--w111-333 flex-col justify-between" v-if="showBubble">
|
||||
<view class="flex-y-center pl-16" @tap="editTo">
|
||||
<text class="iconfont icon-ic_edit"></text>
|
||||
<text class="fs-26 pl-18">编辑</text>
|
||||
</view>
|
||||
<view class="x-line"></view>
|
||||
<view class="flex-y-center pl-16" @tap="delContent">
|
||||
<text class="iconfont icon-ic_delete"></text>
|
||||
<text class="fs-26 pl-18">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-36 flex-col flex-y-center"
|
||||
v-if="currentData && currentData.product && currentData.product.length" @tap="getProList">
|
||||
<image src="@/static/img/discover_cart.png" class="w-66 h-66"></image>
|
||||
</view>
|
||||
<view v-if="showMore && !currentData.product.length" class="h-24"></view>
|
||||
</view>
|
||||
<view class="fixed-desc w-full text--w111-fff" :class="{'hide-tab':showFooter,'full-tab':fullVideo}">
|
||||
<view class="box mb-32">
|
||||
<view class="w-560 lh-42rpx fs-28 fw-500">@{{currentData.author || '用户已注销'}}</view>
|
||||
<view class="w-560 content-box lh-42rpx fs-28 mt-20">
|
||||
<BaseTextMore
|
||||
:content="currentData.title + currentData.content"
|
||||
fontColor="rgba(255,255,255,0.9)"
|
||||
actionFontColor="#fff"
|
||||
:font-size="28"
|
||||
:rows="2"
|
||||
expand-text="展开"
|
||||
collapse-text="收起"></BaseTextMore>
|
||||
</view>
|
||||
<view class="w-560 content-box lh-42rpx fs-28 mt-10 text--w111-fff">
|
||||
<text class="pr-10" v-for="(topic,i) in currentData.topic" :key="i"
|
||||
@tap="authTo('/pages/discover/discoverTopic/index?id=' + topic.id + '&name=' + topic.name)">
|
||||
#{{topic.name}}</text>
|
||||
</view>
|
||||
<scroll-view scroll-x="true" class="white-nowrap vertical-middle w-584 mt-24" show-scrollbar="false"
|
||||
v-if="currentData && currentData.product && currentData.product.length">
|
||||
<view class="inline-block mr-30" v-for="(pro,k) in currentData.product" :key="k">
|
||||
<view class="w-444 bg--w111-fff rd-16rpx p-16 b-e flex-between-center"
|
||||
@tap="goDetail(pro.id)">
|
||||
<image :src="pro.image" class="w-104 h-104 rd-8rpx"></image>
|
||||
<view class="flex-1 pl-14">
|
||||
<view class="w-282 line1 fs-26 pb-20 text--w111-333">{{pro.store_name}}</view>
|
||||
<view class="flex-between-center">
|
||||
<baseMoney :money="pro.price" symbolSize="22" integerSize="32" decimalSize="24" color="#333" weight></baseMoney>
|
||||
<view class="w-92 h-40 rd-20rpx bg-color flex-center text--w111-fff fs-22">购买</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="w-full flex-center pb-32 mt-24 relative z-80"
|
||||
style="left: -20rpx;"
|
||||
v-if="imgList.length && imgList.length > 1">
|
||||
<block v-for="(_, index) in imgList" :key="index">
|
||||
<view class="dot_item h-6 rd-2rpx" :style="{ 'background-color': imgCurrent >= index ? '#fff' : 'rgba(255,255,255,0.4)', width: dotWidth + 'rpx' }"></view>
|
||||
</block>
|
||||
</view>
|
||||
<view v-else class="h-34"></view>
|
||||
</view>
|
||||
<base-drawer
|
||||
mode="bottom"
|
||||
:visible="showProDrawer"
|
||||
zIndex="2000"
|
||||
background-color="transparent"
|
||||
mask
|
||||
maskClosable
|
||||
@close="closeDrawer">
|
||||
<view class="w-full bg--w111-fff text--w111-333 rd-t-40rpx relative">
|
||||
<view class="fs-30 pt-48 pb-44 pl-32">TA提到的宝贝</view>
|
||||
<view class="close-btn flex-center" @tap="closeDrawer()">
|
||||
<text class="iconfont icon-ic_close text--w111-666 fs-24"></text>
|
||||
</view>
|
||||
<view class="px-30">
|
||||
<scroll-view scroll-y="true" style="max-height: 722rpx;">
|
||||
|
||||
<view class="flex-between-center pro-item"
|
||||
v-for="(item,index) in productList" :key="index"
|
||||
@tap="goDetail(item.id)">
|
||||
<view class="flex-1 flex">
|
||||
<image class="w-200 h-200 rd-16rpx" :src='item.image' mode="aspectFill"></image>
|
||||
<view class="h-200 flex-1 flex-col justify-between pl-30">
|
||||
<view>
|
||||
<view class="h-68 lh-34rpx fs-30 line2">{{item.store_name}}</view>
|
||||
<view class="w-full flex items-end flex-wrap mt-16">
|
||||
<BaseTag
|
||||
:text="label.label_name"
|
||||
:color="label.color"
|
||||
:background="label.bg_color"
|
||||
:borderColor="label.border_color"
|
||||
:circle="label.border_color ? true : false"
|
||||
:imgSrc="label.icon"
|
||||
v-for="(label, idx) in item.store_label" :key="idx"></BaseTag>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-between-center">
|
||||
<view class="flex-y-center">
|
||||
<baseMoney :money="item.price" symbolSize="26" integerSize="40" decimalSize="26" weight></baseMoney>
|
||||
<view class="text--w111-999 fs-26 pl-12 text-line">{{item.ot_price}}</view>
|
||||
</view>
|
||||
<view class="w-136 h-52 rd-30rpx flex-center text--w111-fff fs-22 bg-color">立即购买</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!productList.length">
|
||||
<emptyPage title="暂无商品,去看点别的吧~" ></emptyPage>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view :class="{'hide-footer':!showFooter,'show-footer': showFooter}">
|
||||
<view :class="{'h-100':showFooter}"></view>
|
||||
</view>
|
||||
</view>
|
||||
</base-drawer>
|
||||
<replyList
|
||||
:visible="showReply"
|
||||
:community_id="community_id"
|
||||
:comment_num="comment_num"
|
||||
:addReply="addReply"
|
||||
:showFooter="showFooter"
|
||||
@closeDrawer="closeDrawer"
|
||||
@commentAdd="commentAdd"></replyList>
|
||||
<tuiModal
|
||||
:show="showDelete"
|
||||
title="确认删除该内容"
|
||||
:maskClosable="false"
|
||||
@click="handleDelete"></tuiModal>
|
||||
<view class="notice-modal" :style="{'top': sysHeight + 52 + 'px'}" v-if="fullVideo">
|
||||
<view class="w-full h-full container text--w111-fff" v-if="currentData.is_verify == -2" >
|
||||
<view class="flex-y-center fs-28 fw-500">
|
||||
<text class="iconfont icon-icon_clock1 fs-30"></text>
|
||||
<text class="pl-20">平台强制下架,内容仅自己可见</text>
|
||||
</view>
|
||||
<view class="fs-22 pt-14 pl-48">发布的内容涉及政治敏感词汇,请修改后重新发布!</view>
|
||||
</view>
|
||||
<view class="w-full h-full container text--w111-fff" v-if="currentData.is_verify == -1" >
|
||||
<view class="flex-y-center fs-28 fw-500">
|
||||
<text class="iconfont icon-icon_clock1 fs-30"></text>
|
||||
<text class="pl-20">审核未通过,内容仅自己可见</text>
|
||||
</view>
|
||||
<view class="fs-22 pt-14 pl-48">{{currentData.refusal}}</view>
|
||||
</view>
|
||||
<view class="w-full h-full container text--w111-fff" v-if="currentData.is_verify == 0" >
|
||||
<view class="flex-y-center fs-28 fw-500">
|
||||
<text class="iconfont icon-icon_clock1 fs-30"></text>
|
||||
<text class="pl-20">正在审核,内容仅自己可见</text>
|
||||
</view>
|
||||
<view class="fs-22 pt-14 pl-48">发布的内容审核通过后,将在展示首页展示!</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
let sysHeight = uni.getWindowInfo().statusBarHeight;
|
||||
import { HTTP_REQUEST_URL } from '@/config/app';
|
||||
import emptyPage from '@/components/emptyPage.vue';
|
||||
import replyList from "@/components/discoverVideo/replyList.vue"
|
||||
import tuiModal from "@/components/tui-modal/index.vue";
|
||||
import BaseTextMore from "@/components/BaseTextMore.vue"
|
||||
import { getProductslist } from "@/api/store.js";
|
||||
import {
|
||||
communityLikeApi,
|
||||
communitySetInsterestApi,
|
||||
communityDeleteApi,
|
||||
communityShareApi,
|
||||
communityBrowseApi,
|
||||
} from "@/api/community.js"
|
||||
import { mapGetters } from 'vuex';
|
||||
import { toLogin } from "@/libs/login.js"
|
||||
// #ifdef H5
|
||||
import Auth from '@/libs/wechat.js';
|
||||
// #endif
|
||||
export default {
|
||||
name: 'discoverSwiperVideo',
|
||||
props: {
|
||||
swiperData: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
showFooter:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fullVideo:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
replyStatus:{
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
addReply:{
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sysHeight,
|
||||
swiperCurrent: 0,
|
||||
imgList: [],
|
||||
imgCurrent: 0,
|
||||
isPlay: false,
|
||||
showProDrawer:false,
|
||||
productList:[],
|
||||
showBubble:false,
|
||||
showReply:false,
|
||||
community_id:'',
|
||||
comment_num:'',
|
||||
currentData:{
|
||||
author:'',
|
||||
author_image:'',
|
||||
title:'',
|
||||
content:'',
|
||||
product:[],
|
||||
slider_image:[],
|
||||
is_follow:0,
|
||||
is_self:0
|
||||
},
|
||||
showDelete:false,
|
||||
isExpanded:false,
|
||||
startY: 0,
|
||||
moveY: 0,
|
||||
threshold: 50, // 滑动阈值
|
||||
// #ifdef H5
|
||||
Auth,
|
||||
// #endif
|
||||
};
|
||||
},
|
||||
components:{
|
||||
emptyPage,
|
||||
replyList,
|
||||
tuiModal,
|
||||
BaseTextMore
|
||||
},
|
||||
watch:{
|
||||
swiperData:{
|
||||
handler(nVal){
|
||||
this.currentData = nVal[this.swiperCurrent];
|
||||
if(this.currentData && this.currentData.slider_image && this.currentData.slider_image.length){
|
||||
this.imgList = this.currentData.slider_image;
|
||||
}
|
||||
//初始化记录第一个浏览的内容
|
||||
if(this.currentData){
|
||||
this.lookVideo();
|
||||
}
|
||||
},
|
||||
immediate:true
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isLogin','uid']),
|
||||
dotWidth() {
|
||||
let windowWidth = uni.getWindowInfo().windowWidth;
|
||||
return (windowWidth * 2 - (20 + (this.imgList.length) * 12)) / this.imgList.length;
|
||||
},
|
||||
showMore(){
|
||||
if(this.currentData && this.currentData.relation_id == this.uid && this.isLogin){
|
||||
return true
|
||||
}else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTouchStart(e) {
|
||||
this.startY = e.touches[0].pageY;
|
||||
},
|
||||
handleTouchMove(e) {
|
||||
this.moveY = e.touches[0].pageY;
|
||||
},
|
||||
handleTouchEnd() {
|
||||
const diffY = this.moveY - this.startY;
|
||||
if (diffY > this.threshold) {
|
||||
if (this.swiperCurrent > 0) {
|
||||
this.videoChange({detail: {current: this.swiperCurrent - 1}});
|
||||
}
|
||||
} else if (diffY < -this.threshold) {
|
||||
this.videoChange({detail: {current: this.swiperCurrent + 1}});
|
||||
}
|
||||
},
|
||||
backPage(){
|
||||
let pages = getCurrentPages(); // 获取当前打开过的页面路由数,
|
||||
if (pages.length > 1) {
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
}
|
||||
},
|
||||
playVideo(){
|
||||
uni.createVideoContext(this.swiperData[this.swiperCurrent].id + 'k' + this.swiperCurrent, this).play();
|
||||
this.isPlay = true;
|
||||
},
|
||||
tapVideoHover(e) {
|
||||
if(this.isPlay){
|
||||
uni.createVideoContext(this.swiperData[this.swiperCurrent].id + 'k' + this.swiperCurrent, this).pause();
|
||||
this.isPlay = false;
|
||||
}else{
|
||||
uni.createVideoContext(this.swiperData[this.swiperCurrent].id + 'k' + this.swiperCurrent, this).play();
|
||||
this.isPlay = true;
|
||||
}
|
||||
|
||||
},
|
||||
ended() {},
|
||||
videoChange(event) {
|
||||
console.log(event);
|
||||
const newIndex = event.detail.current;
|
||||
// 暂停当前视频
|
||||
if (this.swiperCurrent !== newIndex) {
|
||||
uni.createVideoContext(this.swiperData[this.swiperCurrent].id + 'k' + this.swiperCurrent, this).pause();
|
||||
this.swiperCurrent = newIndex;
|
||||
// 播放新视频
|
||||
uni.createVideoContext(this.swiperData[this.swiperCurrent].id + 'k' + this.swiperCurrent, this).play();
|
||||
this.currentData = this.swiperData[this.swiperCurrent];
|
||||
this.showBubble = false;
|
||||
this.$emit('onSwiper',this.swiperCurrent);
|
||||
}
|
||||
if(this.swiperData[this.swiperCurrent].slider_image.length){
|
||||
this.imgList = this.swiperData[this.swiperCurrent].slider_image;
|
||||
|
||||
}else{
|
||||
this.imgList = [];
|
||||
}
|
||||
this.imgCurrent = 0;
|
||||
this.lookVideo();
|
||||
},
|
||||
lookVideo(){
|
||||
if(this.isLogin){
|
||||
communityBrowseApi(this.currentData.id).catch(err=>{
|
||||
console.error(err);
|
||||
})
|
||||
}
|
||||
},
|
||||
photoChange(e) {
|
||||
this.imgCurrent = e.detail.current;
|
||||
},
|
||||
goDetail(id){
|
||||
let url = `/pages/goods_details/index?id=${id}`
|
||||
this.$util.JumpPath(url);
|
||||
},
|
||||
editTo(){
|
||||
uni.navigateTo({
|
||||
url: '/pages/discover/discoverCreate/index?id=' + this.currentData.id
|
||||
})
|
||||
},
|
||||
delContent(){
|
||||
this.showDelete = true;
|
||||
this.showBubble = false;
|
||||
},
|
||||
handleDelete(e){
|
||||
let index = e.index;
|
||||
let that = this;
|
||||
if(index == 1){
|
||||
communityDeleteApi(this.currentData.id).then(res=>{
|
||||
this.showDelete = false;
|
||||
return this.$util.Tips({
|
||||
title: res.msg
|
||||
},{
|
||||
tab:4,
|
||||
url:'/pages/discoverIndex/index'
|
||||
});
|
||||
}).catch(err => {
|
||||
this.showDelete = false;
|
||||
return this.$util.Tips({
|
||||
title: err
|
||||
});
|
||||
});
|
||||
}else{
|
||||
this.showDelete = false;
|
||||
}
|
||||
},
|
||||
getProList(){
|
||||
let data = {
|
||||
ids: this.currentData.product_id.toString()
|
||||
};
|
||||
getProductslist(data).then(res=>{
|
||||
this.productList = res.data;
|
||||
this.showProDrawer = true;
|
||||
})
|
||||
},
|
||||
toUser(){
|
||||
uni.navigateTo({
|
||||
url: '/pages/discover/discoverUser/index?id=' + this.currentData.relation_id
|
||||
})
|
||||
},
|
||||
authTo(url){
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
});
|
||||
},
|
||||
toggleReplyDrawer(){
|
||||
this.community_id = this.currentData.id;
|
||||
this.comment_num = this.currentData.comment_num;
|
||||
if(this.isLogin){
|
||||
this.showReply = true;
|
||||
}else{
|
||||
toLogin();
|
||||
}
|
||||
|
||||
},
|
||||
contentLike(){
|
||||
if(!this.isLogin) {
|
||||
toLogin();
|
||||
}
|
||||
let id = this.currentData.id;
|
||||
let status = this.currentData.is_like == 1 ? 0 : 1;
|
||||
let that = this;
|
||||
communityLikeApi(id,{status}).then(res=>{
|
||||
that.$emit('onLike',{index:that.swiperCurrent,status:status});
|
||||
}).catch(err=>{
|
||||
return this.$util.Tips({
|
||||
title: err
|
||||
});
|
||||
})
|
||||
},
|
||||
closeDrawer(){
|
||||
this.showReply = false;
|
||||
this.showProDrawer = false;
|
||||
},
|
||||
followFun(){
|
||||
if(!this.isLogin) {
|
||||
toLogin();
|
||||
}
|
||||
communitySetInsterestApi(this.currentData.relation_id,{status:1}).then(res=>{
|
||||
this.$emit('followChange',{index:this.swiperCurrent});
|
||||
}).catch(err => {
|
||||
return this.$util.Tips({
|
||||
title: err
|
||||
});
|
||||
});
|
||||
},
|
||||
commentAdd(data){
|
||||
if(data.type == 1){
|
||||
this.comment_num++;
|
||||
}else{
|
||||
this.comment_num = this.comment_num - data.num;
|
||||
}
|
||||
},
|
||||
openShare(){
|
||||
let that = this;
|
||||
if(that.isLogin){
|
||||
communityShareApi(that.currentData.id).catch(err=>{
|
||||
console.error(err);
|
||||
})
|
||||
// #ifdef H5
|
||||
uni.setClipboardData({
|
||||
data: `${HTTP_REQUEST_URL}/pages/discover/discoverVideo/index?id=${that.currentData.id}&spid=${that.uid}`,
|
||||
success: () => {
|
||||
uni.showToast({
|
||||
title: '链接已复制'
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
this.$emit('onShare',that.currentData);
|
||||
// #endif
|
||||
}else{
|
||||
toLogin();
|
||||
}
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.video-swiper {
|
||||
height: 100vh;
|
||||
}
|
||||
.z-4000{
|
||||
z-index: 4000;
|
||||
}
|
||||
.avatar-box{
|
||||
border: 3rpx solid #FFFFFF;
|
||||
}
|
||||
.bg-transparent{
|
||||
background-color: transparent;
|
||||
}
|
||||
.text-line{
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.hide-footer{
|
||||
padding-bottom: 30rpx;
|
||||
}
|
||||
.show-footer{
|
||||
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.right-action {
|
||||
position: fixed;
|
||||
right: 20rpx;
|
||||
/* #ifdef H5 */
|
||||
bottom: 162rpx;
|
||||
/* #endif */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
bottom: 62rpx;
|
||||
/* #endif */
|
||||
width: 88rpx;
|
||||
}
|
||||
.action-box{
|
||||
/* #ifdef MP-WEIXIN */
|
||||
bottom: calc(162rpx + env(safe-area-inset-bottom));
|
||||
/* #endif */
|
||||
}
|
||||
.full-action{
|
||||
/* #ifdef H5 */
|
||||
bottom: 64rpx !important;
|
||||
/* #endif */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
bottom: calc(64rpx + env(safe-area-inset-bottom)) !important;
|
||||
/* #endif */
|
||||
}
|
||||
.shadow{
|
||||
text-shadow: 0px 2rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.fixed-desc {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
padding-left: 20rpx;
|
||||
z-index: 10;
|
||||
/* #ifdef H5 */
|
||||
bottom: 100rpx;
|
||||
/* #endif */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
bottom: 0;
|
||||
/* #endif */
|
||||
background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.3) 100%);
|
||||
.box {
|
||||
width: 584rpx;
|
||||
}
|
||||
.w-584 {
|
||||
width: 584rpx;
|
||||
}
|
||||
}
|
||||
.content-box{
|
||||
max-height: 400rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.hide-tab{
|
||||
/* #ifdef MP-WEIXIN */
|
||||
bottom: calc(100rpx + env(safe-area-inset-bottom));
|
||||
/* #endif */
|
||||
}
|
||||
.full-tab{
|
||||
/* #ifdef H5 */
|
||||
bottom: 0 !important;
|
||||
/* #endif */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
bottom: env(safe-area-inset-bottom) !important;
|
||||
/* #endif */
|
||||
}
|
||||
.dot_item ~ .dot_item {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
.follow-icon{
|
||||
position: absolute;
|
||||
bottom: -10rpx;
|
||||
left: 24rpx;
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
border-radius: 50%;
|
||||
background-color: var(--view-theme);
|
||||
|
||||
}
|
||||
.like-heart{
|
||||
color: #e93323;
|
||||
}
|
||||
.pro-item ~ .pro-item{
|
||||
margin-top: 42rpx;
|
||||
}
|
||||
|
||||
.bubble_box{
|
||||
position: absolute;
|
||||
top: -64rpx;
|
||||
left: -208rpx;
|
||||
width: 184rpx;
|
||||
z-index: 999;
|
||||
.bubble{
|
||||
width: 184rpx;
|
||||
height: 198rpx;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0rpx 2rpx 15rpx 0rpx rgba(0,0,0,0.102);
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx 16rpx 30rpx 16rpx;
|
||||
position: relative;
|
||||
&:after{
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: 74rpx;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
border-left: 10px solid #fff;
|
||||
}
|
||||
}
|
||||
.x-line{
|
||||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.10);
|
||||
}
|
||||
}
|
||||
.notice-modal{
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 690rpx;
|
||||
background: rgba(51, 51, 51, 0.7);
|
||||
border-radius: 10rpx;
|
||||
.container{
|
||||
padding: 26rpx 30rpx;
|
||||
.icon-icon_clock1{
|
||||
color: rgba(252, 131, 39, 1);
|
||||
}
|
||||
.icon-a-ic_tanhao1{
|
||||
color: rgba(233, 51, 35, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
.close-btn{
|
||||
position: absolute;
|
||||
right: 28rpx;
|
||||
top: 28rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #eee;
|
||||
|
||||
}
|
||||
</style>
|
||||
344
components/discoverVideo/replyList.vue
Normal file
344
components/discoverVideo/replyList.vue
Normal file
@@ -0,0 +1,344 @@
|
||||
<script>
|
||||
import baseDrawer from '@/components/tui-drawer/tui-drawer.vue';
|
||||
import emptyPage from '@/components/emptyPage.vue';
|
||||
import tuiModal from "@/components/tui-modal/index.vue"
|
||||
import { mapGetters } from "vuex";
|
||||
import {
|
||||
communityReplySaveApi,
|
||||
communityReplyListApi,
|
||||
communityReplyLikeApi,
|
||||
communityReplyDeleteApi,
|
||||
} from "@/api/community.js";
|
||||
export default {
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
community_id:{
|
||||
type: Number | String,
|
||||
default: ''
|
||||
},
|
||||
comment_num:{
|
||||
type: Number | String,
|
||||
default: 0
|
||||
},
|
||||
ensureInfo: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
showFooter:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
addReply:{
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
comment:'',
|
||||
comment_reply_id:'',
|
||||
replyIndex:'',
|
||||
replyList:[],
|
||||
placeholder:'快来说点儿什么吧...',
|
||||
showModal:false,
|
||||
reduceNum:0,
|
||||
focus:false,
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
community_id(val){
|
||||
if(val){
|
||||
this.getReply()
|
||||
}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
baseDrawer,
|
||||
emptyPage,
|
||||
tuiModal
|
||||
},
|
||||
computed:{
|
||||
...mapGetters(['isLogin','uid']),
|
||||
},
|
||||
methods: {
|
||||
closeDrawer() {
|
||||
this.$emit('closeDrawer');
|
||||
},
|
||||
getReply(){
|
||||
let params = {
|
||||
community_id:this.community_id,
|
||||
reply_id:'',
|
||||
};
|
||||
communityReplyListApi(params).then(res=>{
|
||||
if(res.data.list.length){
|
||||
res.data.list.map(item=>{
|
||||
this.$set(item,'show',false);
|
||||
})
|
||||
this.replyList = res.data.list;
|
||||
}else{
|
||||
this.replyList = [];
|
||||
}
|
||||
}).catch(err => {
|
||||
return this.$util.Tips({
|
||||
title: err
|
||||
});
|
||||
});
|
||||
},
|
||||
sendText(){
|
||||
if(!this.isLogin) return this.$util.Tips({
|
||||
title: '请登录'
|
||||
});
|
||||
if(this.comment == '') return this.$util.Tips({
|
||||
title: '请输入评论内容'
|
||||
});
|
||||
let data = {
|
||||
community_id:this.community_id,
|
||||
comment_reply_id:this.comment_reply_id,
|
||||
content:this.comment
|
||||
};
|
||||
communityReplySaveApi(data).then(res=>{
|
||||
this.comment_reply_id = '';
|
||||
this.comment = '';
|
||||
this.placeholder = '快来说点儿什么吧...';
|
||||
this.$emit('onCommentAdd',{type:1,num:1});
|
||||
this.getReply();
|
||||
this.hide();
|
||||
return this.$util.Tips({
|
||||
title: res.msg
|
||||
});
|
||||
}).catch(err => {
|
||||
return this.$util.Tips({
|
||||
title: err
|
||||
});
|
||||
});
|
||||
},
|
||||
showReply(id,author){
|
||||
this.comment_reply_id = id;
|
||||
this.placeholder = `回复:${author}`;
|
||||
},
|
||||
showMore(item,index,type){
|
||||
this.replyIndex = index;
|
||||
if(type){
|
||||
let params = {
|
||||
community_id:this.community_id,
|
||||
reply_id:item.id,
|
||||
};
|
||||
communityReplyListApi(params).then(res=>{
|
||||
this.$set(this.replyList[index],'children',res.data.list);
|
||||
this.$set(item,'show',true);
|
||||
his.$util.Tips({
|
||||
title: res.msg
|
||||
});
|
||||
}).catch(err => {
|
||||
return this.$util.Tips({
|
||||
title: err
|
||||
});
|
||||
});
|
||||
}else{
|
||||
this.$set(item,'show',false);
|
||||
}
|
||||
},
|
||||
authTo(url){
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
});
|
||||
},
|
||||
replyLike(item){
|
||||
if(!this.isLogin) return this.$util.Tips({
|
||||
title: '请登录'
|
||||
});
|
||||
let id = item.id;
|
||||
let status = item.isLike == 1 ? 0 : 1;
|
||||
let that = this;
|
||||
communityReplyLikeApi(id,{status}).then(res=>{
|
||||
item.isLike = status;
|
||||
if(status == 1){
|
||||
item.like_num++
|
||||
}else{
|
||||
item.like_num--
|
||||
}
|
||||
return that.$util.Tips({
|
||||
title: res.msg
|
||||
});
|
||||
}).catch(err=>{
|
||||
return that.$util.Tips({
|
||||
title: err
|
||||
});
|
||||
})
|
||||
},
|
||||
delReply(id, num){
|
||||
this.comment_reply_id = id;
|
||||
this.reduceNum = num;
|
||||
this.showModal = true;
|
||||
},
|
||||
handleClick(e){
|
||||
let index = e.index;
|
||||
let that = this;
|
||||
if(index == 1){
|
||||
communityReplyDeleteApi(this.comment_reply_id).then(res=>{
|
||||
this.showModal = false;
|
||||
this.replyList = [];
|
||||
this.comment_reply_id = '';
|
||||
this.$emit('commentAdd',{type:2,num:1 + this.reduceNum});
|
||||
this.getReply();
|
||||
}).catch(err => {
|
||||
this.showModal = false;
|
||||
return this.$util.Tips({
|
||||
title: err
|
||||
});
|
||||
});
|
||||
}else{
|
||||
this.showModal = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<view>
|
||||
<base-drawer
|
||||
mode="bottom"
|
||||
:visible="visible"
|
||||
zIndex="3000"
|
||||
background-color="transparent"
|
||||
mask maskClosable
|
||||
@close="closeDrawer">
|
||||
<view class="w-full bg--w111-fff relative rd-t-40rpx pt-32">
|
||||
<view class="text-center fs-32 text--w111-333 fw-500 mb-30">评论 {{comment_num}}</view>
|
||||
<view class="close-btn flex-center" @tap="closeDrawer()">
|
||||
<text class="iconfont icon-ic_close text--w111-666 fs-24"></text>
|
||||
</view>
|
||||
<scroll-view scroll-y="true" class="scroll-content" v-show="!focus">
|
||||
<view class="px-30">
|
||||
<view class="reply-list" v-if="replyList.length">
|
||||
<view class="flex mt-22" v-for="(item,index) in replyList" :key="index"
|
||||
v-show="(item.uid == uid && item.is_show == 0) || item.is_show == 1">
|
||||
<image class="w-74 h-74 rd-50-p111-" :src="item.author_image"
|
||||
@tap="authTo('/pages/discover/discoverUser/index?id=' + item.uid)"></image>
|
||||
<view class="flex-1 ml-20">
|
||||
<view class="w-full">
|
||||
<view class="fs-26 text--w111-999">{{item.author}}</view>
|
||||
<view class="fs-28 lh-42rpx text--w111-333" @tap="showReply(item.id,item.author,item.is_show)">{{item.content}}
|
||||
<text class="pl-8 text--w111-999" v-show="!item.is_show && item.uid == uid">(该评论已被屏蔽)</text>
|
||||
</view>
|
||||
<view class="lh-36rpx flex-between-center pt-4">
|
||||
<view class="flex-y-center fs-24">
|
||||
<text class="text--w111-ccc">{{item.time_text}}</text>
|
||||
<text class="pl-24 text--w111-999" @tap="delReply(item.id,item.comment_num)"
|
||||
v-show="isLogin && uid == item.uid">删除</text>
|
||||
</view>
|
||||
<view class="flex-y-center text--w111-999" @tap="replyLike(item)">
|
||||
<text class="iconfont fs-30" :class="item.isLike ? 'icon-icon_Like_2' : 'icon-ic_Like'"></text>
|
||||
<text class="fs-24 lh-36rpx pl-10">{{item.like_num}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-12 w-full" v-if="item.show">
|
||||
<view class="child-reply flex justify-between" v-for="(reply,i) in item.children" :key="i"
|
||||
v-show="(reply.uid == uid && reply.is_show == 0) || reply.is_show == 1">
|
||||
<view class="flex w-full" >
|
||||
<image :src="reply.author_image" class="w-36 h-36 rd-50-p111-"
|
||||
@tap="authTo('/pages/discover/discoverUser/index?id=' + reply.uid)"></image>
|
||||
<view class="ml-20 flex-1">
|
||||
<view class="text--w111-999 fs-26 lh-40rpx">{{reply.author}}</view>
|
||||
<view class="w-full fs-26 lh-42 flex-y-center flex-wrap text--w111-333" @tap="showReply(reply.id, reply.author)">
|
||||
<view v-if="reply.comment_author">
|
||||
回复<text class="comment-author"
|
||||
@tap="authTo('/pages/discover/discoverUser/index?id=' + reply.comment_reply_uid)">@{{reply.comment_author}}</text>
|
||||
</view>
|
||||
{{reply.content}}<text class="pl-8 text--w111-999" v-show="!item.is_show && item.uid == uid">(该评论已被屏蔽)</text>
|
||||
</view>
|
||||
<view class="flex-between-center pt-4">
|
||||
<view class="fs-24 text--w111-ccc lh-36rpx flex-y-center">
|
||||
<text>{{reply.time_text}}</text>
|
||||
<text class="pl-24 text--w111-999" @tap="delReply(reply.id,reply.comment_num)"
|
||||
v-show="isLogin && uid == reply.uid">删除</text>
|
||||
</view>
|
||||
<view class="flex-y-center text--w111-999" @tap.stop="replyLike(reply)">
|
||||
<text class="iconfont fs-30" :class="reply.isLike ? 'icon-icon_Like_2' : 'icon-ic_Like'"></text>
|
||||
<text class="fs-24 lh-36rpx pl-10">{{reply.like_num}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mt-14 flex-y-center text--w111-666 fs-26"
|
||||
v-if="item.comment_num > 0 && !item.show" @tap="showMore(item,index,1)">
|
||||
<text class="more-line"></text>
|
||||
<text class="pl-16">展开{{item.comment_num}}条回复</text>
|
||||
<text class="iconfont icon-ic_downarrow fs-28"></text>
|
||||
</view>
|
||||
<view class="mt-14 flex-y-center text--w111-666 fs-26"
|
||||
v-if="item.comment_num > 0 && item.show" @tap="showMore(item,index,0)">
|
||||
<text class="more-line"></text>
|
||||
<text class="pl-16">收起回复</text>
|
||||
<text class="iconfont icon-ic_uparrow fs-28"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else>
|
||||
<emptyPage title="暂无评论,快去抢沙发吧~" src="/statics/images/noActivity.gif"></emptyPage>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="h-100" v-if="addReply"></view>
|
||||
<view class="fixed-lb w-full pb-safe" :class="{'hide-bar': showFooter}" v-if="addReply">
|
||||
<view class="h-100 px-30 flex-between-center bg--w111-fff">
|
||||
<input type="text" v-model="comment" :placeholder="placeholder"
|
||||
:focus="focus"
|
||||
placeholder-class="text--w111-999"
|
||||
class="flex-1 h-64 rd-32rpx bg--w111-f5f5f5 pl-30 fs-26 text--w111-333" />
|
||||
<view class="w-112 h-64 rd-32rpx bg-gradient flex-center fs-24 text--w111-fff ml-20" @tap="sendText">发送</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</base-drawer>
|
||||
<!-- 确认框 -->
|
||||
<tuiModal
|
||||
:show="showModal"
|
||||
title="确认删除该评论"
|
||||
:maskClosable="false"
|
||||
@click="handleClick"></tuiModal>
|
||||
</view>
|
||||
</template>
|
||||
<style>
|
||||
.scroll-content{
|
||||
height: 900rpx;
|
||||
}
|
||||
.hide-bar{
|
||||
bottom: 100rpx;
|
||||
}
|
||||
.mb-54{
|
||||
margin-bottom: 54rpx;
|
||||
}
|
||||
.child-reply ~ .child-reply{
|
||||
margin-top: 22rpx;
|
||||
}
|
||||
.more-line{
|
||||
width: 40rpx;
|
||||
height: 2rpx;
|
||||
background: #D8D8D8;
|
||||
}
|
||||
.comment-author{
|
||||
color: #4A8AC9;
|
||||
padding: 0 4rpx;
|
||||
}
|
||||
.icon-icon_Like_2{
|
||||
color: #e93323;
|
||||
}
|
||||
.close-btn{
|
||||
position: absolute;
|
||||
right: 28rpx;
|
||||
top: 28rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #eee;
|
||||
|
||||
}
|
||||
</style>
|
||||
225
components/discoverWaterfall/WaterfallsFlow.vue
Normal file
225
components/discoverWaterfall/WaterfallsFlow.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<view :class="'wf-page wf-page'+type">
|
||||
<!-- left -->
|
||||
<view>
|
||||
<view id="left" v-if="leftList.length">
|
||||
<view v-for="(item,index) in leftList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem :item="item" :border="border" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- right -->
|
||||
<view>
|
||||
<view id="right" v-if="rightList.length">
|
||||
<view v-for="(item,index) in rightList" :key="index"
|
||||
class="wf-item" @tap="itemTap(item)">
|
||||
<WaterfallsFlowItem :item="item" :border="border" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WaterfallsFlowItem from './WaterfallsFlowItem.vue';
|
||||
import {LOGIN_STATUS} from '@/config/cache';
|
||||
import { HTTP_REQUEST_URL } from '@/config/app';
|
||||
export default {
|
||||
components: {
|
||||
WaterfallsFlowItem
|
||||
},
|
||||
props: {
|
||||
// 瀑布流列表
|
||||
wfList: {
|
||||
type: Array,
|
||||
require: true
|
||||
},
|
||||
type: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
updateNum: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
border:{
|
||||
type:Boolean,
|
||||
default: false
|
||||
},
|
||||
isSelf:{
|
||||
type:Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allList: [], // 全部列表
|
||||
leftList: [], // 左边列表
|
||||
rightList: [], // 右边列表
|
||||
mark: 0, // 列表标记
|
||||
boxHeight: [], // 下标0和1分别为左列和右列高度
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
// 监听列表数据变化
|
||||
wfList: {
|
||||
handler(nVal,oVal){
|
||||
// 如果数据为空或新的列表数据少于旧的列表数据(通常为下拉刷新或切换排序或使用筛选器),初始化变量
|
||||
|
||||
if (!this.wfList.length ||
|
||||
(this.wfList.length === this.updateNum && this.wfList.length <= this.allList.length)) {
|
||||
this.allList = [];
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.mark = 0;
|
||||
}
|
||||
|
||||
// 如果列表有值,调用waterfall方法
|
||||
|
||||
if (this.wfList.length) {
|
||||
this.allList = this.wfList;
|
||||
this.leftList = [];
|
||||
this.rightList = [];
|
||||
this.boxHeight = [];
|
||||
this.allList.forEach((v, i) => {
|
||||
if(this.allList.length < 3 || (this.allList.length <= 7 && this.allList.length - i > 1) || (this.allList.length > 7 && this.allList.length - i > 2)) {
|
||||
if(i % 2){
|
||||
this.rightList.push(v);
|
||||
}else{
|
||||
this.leftList.push(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
if(this.allList.length < 3){
|
||||
this.mark = this.allList.length+1;
|
||||
}else if(this.allList.length <= 7){
|
||||
this.mark = this.allList.length - 1;
|
||||
}else{
|
||||
this.mark = this.allList.length - 2;
|
||||
}
|
||||
if(this.mark < this.allList.length){
|
||||
this.waterFall()
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep:true
|
||||
},
|
||||
mounted(){
|
||||
},
|
||||
|
||||
// 监听标记,当标记发生变化,则执行下一个item排序
|
||||
mark() {
|
||||
const len = this.allList.length;
|
||||
if (this.mark < len && this.mark !== 0 && this.boxHeight.length) {
|
||||
this.waterFall();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 瀑布流排序
|
||||
waterFall() {
|
||||
const i = this.mark;
|
||||
if (i == 0) {
|
||||
// 初始化,从左边开始插入
|
||||
this.leftList.push(this.allList[i]);
|
||||
// 更新左边列表高度
|
||||
this.getViewHeight(0);
|
||||
} else if (i == 1) {
|
||||
// 第二个item插入,默认为右边插入
|
||||
this.rightList.push(this.allList[i]);
|
||||
// 更新右边列表高度
|
||||
this.getViewHeight(1);
|
||||
} else {
|
||||
// 根据左右列表高度判断下一个item应该插入哪边
|
||||
if(!this.boxHeight.length){
|
||||
this.rightList.length < this.leftList.length
|
||||
? this.rightList.push(this.allList[i])
|
||||
: this.leftList.push(this.allList[i]);
|
||||
} else {
|
||||
const leftOrRight = this.boxHeight[0] > this.boxHeight[1] ? 1 : 0;
|
||||
if (leftOrRight) {
|
||||
this.rightList.push(this.allList[i])
|
||||
} else {
|
||||
this.leftList.push(this.allList[i])
|
||||
}
|
||||
}
|
||||
// 更新插入列表高度
|
||||
this.getViewHeight();
|
||||
}
|
||||
},
|
||||
// 获取列表高度
|
||||
getViewHeight() {
|
||||
// 使用nextTick,确保页面更新结束后,再请求高度
|
||||
this.$nextTick(() => {
|
||||
setTimeout(()=>{
|
||||
uni.createSelectorQuery().in(this).select('#right').boundingClientRect(res => {
|
||||
res ? this.boxHeight[1] = res.height : '';
|
||||
uni.createSelectorQuery().in(this).select('#left').boundingClientRect(res => {
|
||||
res ? this.boxHeight[0] = res.height : '';
|
||||
this.mark = this.mark + 1;
|
||||
}).exec();
|
||||
}).exec();
|
||||
},100)
|
||||
})
|
||||
},
|
||||
// item点击
|
||||
itemTap(item) {
|
||||
if(item.content_type == 1){
|
||||
uni.navigateTo({
|
||||
url: '/pages/discover/discoverDetails/index?id=' + item.id
|
||||
})
|
||||
}else{
|
||||
let that = this;
|
||||
if(this.isSelf){
|
||||
uni.navigateTo({
|
||||
// #ifdef MP || H5
|
||||
url: '/pages/discover/discoverVideo/index?id=' + item.id + '&relation_id=' + item.relation_id
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
url: `/pages/discover/discoverVideo/app?url=${HTTP_REQUEST_URL}/pages/discover/discoverVideo/index?content_type=2&token=${that.$Cache.get(LOGIN_STATUS)}&relation_id=${item.relation_id}`
|
||||
// #endif
|
||||
})
|
||||
}else{
|
||||
// #ifdef MP || H5
|
||||
uni.navigateTo({
|
||||
url: '/pages/discover/discoverVideo/index?id=' + item.id
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
let href = encodeURIComponent(`${HTTP_REQUEST_URL}/pages/discover/discoverVideo/index?content_type=2&id=${item.id}&token=${that.$Cache.get(LOGIN_STATUS)}`)
|
||||
uni.navigateTo({
|
||||
url: `/pages/discover/discoverVideo/app?url=${href}`
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$page-padding: 10px;
|
||||
$grid-gap: 10px;
|
||||
|
||||
.wf-page {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: $grid-gap;
|
||||
}
|
||||
.wf-item {
|
||||
width: calc((100vw - 2 * #{$page-padding} - #{$grid-gap}) / 2);
|
||||
padding-bottom: $grid-gap;
|
||||
}
|
||||
.wf-page1 .wf-item{
|
||||
margin-top: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
130
components/discoverWaterfall/WaterfallsFlowItem.vue
Normal file
130
components/discoverWaterfall/WaterfallsFlowItem.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<view class="wf-item-page wf-page0">
|
||||
<view class='pictrue relative'>
|
||||
<easy-loadimage
|
||||
mode="widthFix"
|
||||
:image-src="item.image"
|
||||
width="100%"
|
||||
borderRadius="16rpx 16rpx 0 0"></easy-loadimage>
|
||||
<view class="player flex-center" v-if="item.content_type == 2">
|
||||
<text class="iconfont icon-ic_right2 fs-20"></text>
|
||||
</view>
|
||||
<view class="w-60 h-36 rd-4rpx flex-center fs-22 text--w111-fff pic-number"
|
||||
v-if="item.content_type == 1">{{item.slider_image.length}}图</view>
|
||||
<view class="abs-lt w-full h-full flex-col flex-center text--w111-fff shenhe"
|
||||
v-if="[0,-1,-2].includes(item.is_verify)">
|
||||
<text class="fs-28" v-show="item.is_verify == 0">正在审核</text>
|
||||
<text class="fs-24 pt-22" v-show="item.is_verify == 0">通过后将展示在列表</text>
|
||||
<text class="fs-28" v-show="item.is_verify == -1">审核未通过</text>
|
||||
<text class="fs-24 pt-22" v-show="item.is_verify == -1">查看未通过原因</text>
|
||||
<text class="fs-28" v-show="item.is_verify == -2">强制下架</text>
|
||||
<text class="fs-24 pt-22" v-show="item.is_verify == -2">查看下架原因</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info_box" :class="{'box-border': border}">
|
||||
<view class="w-full lh-40rpx fs-28 line2">{{item.title}}</view>
|
||||
<view class="pt-22 fs-22 text--w111-999 flex-between-center">
|
||||
<view class="flex-y-center" @tap.stop="toUser">
|
||||
<image class="w-34 h-34 rd-50-p111-" :src="item.author_image || '/static/images/f.png'"></image>
|
||||
<text class="pl-8 w-180 line1">{{item.author || '用户已注销'}}</text>
|
||||
</view>
|
||||
<view class="flex-y-center" :class="{'text-red': item.is_like}" @tap.stop="contentLike">
|
||||
<text class="iconfont fs-24" :class="item.is_like ? 'icon-icon_Like_2' : 'icon-ic_Like'"></text>
|
||||
<text class="pl-10">{{item.like_num}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import easyLoadimage from '@/components/easy-loadimage/easy-loadimage.vue';
|
||||
import { communityLikeApi } from "@/api/community.js";
|
||||
import { mapGetters } from "vuex";
|
||||
import { HTTP_REQUEST_URL } from '@/config/app';
|
||||
export default {
|
||||
components: {
|
||||
easyLoadimage
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
require: true
|
||||
},
|
||||
border:{
|
||||
type:Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
inject: ['flowLike'],
|
||||
data() {
|
||||
return {
|
||||
domain: HTTP_REQUEST_URL,
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
...mapGetters(['isLogin','uid']),
|
||||
},
|
||||
methods: {
|
||||
toUser(){
|
||||
uni.navigateTo({
|
||||
url: '/pages/discover/discoverUser/index?id=' + this.item.relation_id
|
||||
})
|
||||
},
|
||||
contentLike(){
|
||||
if(!this.isLogin) return this.$util.Tips({
|
||||
title: '请登录'
|
||||
});
|
||||
let status = this.item.is_like == 1 ? 0 : 1;
|
||||
let that = this;
|
||||
communityLikeApi(this.item.id,{status}).then(res=>{
|
||||
that.flowLike({id:that.item.id,status:status})
|
||||
}).catch(err=>{
|
||||
return this.$util.Tips({
|
||||
title: err
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.wf-item-page {
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
.pictrue{
|
||||
max-height: 500rpx;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.info_box{
|
||||
padding: 24rpx;
|
||||
border-radius: 0 0 24rpx 24rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
.box-border{
|
||||
border: 1rpx solid #eee;
|
||||
}
|
||||
.text-red{
|
||||
color: #e93323;
|
||||
}
|
||||
.player{
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(51, 51, 51, 0.5);
|
||||
color: #fff;
|
||||
}
|
||||
.shenhe{
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
.pic-number{
|
||||
position: absolute;
|
||||
right: 16rpx;
|
||||
bottom: 16rpx;
|
||||
background: rgba(102, 102, 102, 0.50);
|
||||
}
|
||||
</style>
|
||||
267
components/easy-loadimage/easy-loadimage.vue
Normal file
267
components/easy-loadimage/easy-loadimage.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<view class="easy-loadimage" :style="[boxStyle]" :id="uid">
|
||||
<image class="origin-img block" :src="imageSrc" :mode="mode"
|
||||
v-if="loadImg&&!isLoadError"
|
||||
v-show="showImg"
|
||||
:style="[imgStyle]"
|
||||
:class="{'no-transition':!openTransition,'show-transition':showTransition&&openTransition}"
|
||||
@load="handleImgLoad" @error="handleImgError">
|
||||
</image>
|
||||
<image class="border-img" :src="borderSrc" mode="scaleToFill"
|
||||
v-if="loadImg&&!isLoadError&&borderSrc"
|
||||
v-show="showImg"
|
||||
:style="[imgStyle]"
|
||||
:class="{'no-transition':!openTransition,'show-transition':showTransition&&openTransition}">
|
||||
</image>
|
||||
<view class="loadfail-img" v-else-if="isLoadError"></view>
|
||||
<view :class="['loading-img','spin-circle',loadingMode,mode]" v-show="!showImg&&!isLoadError"></view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import {throttle} from '@/utils/validate.js';
|
||||
|
||||
// 生成全局唯一id
|
||||
function generateUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
let r = Math.random() * 16 | 0,
|
||||
v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
})
|
||||
}
|
||||
export default {
|
||||
props: {
|
||||
imageSrc: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
borderSrc: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: "aspectFill"
|
||||
},
|
||||
loadingMode: {
|
||||
type: String,
|
||||
default: 'looming-gray'
|
||||
},
|
||||
openTransition: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
viewHeight: {
|
||||
type: Number,
|
||||
default () {
|
||||
return uni.getWindowInfo().windowHeight;
|
||||
}
|
||||
},
|
||||
width:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
height:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
borderRadius:{
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const that = this;
|
||||
return {
|
||||
// uid:'',
|
||||
uid: 'uid-' + generateUUID(),
|
||||
loadImg: false,
|
||||
showImg: false,
|
||||
isLoadError: false,
|
||||
borderLoaded: 0,
|
||||
showTransition: false,
|
||||
scrollFn: throttle(function() {
|
||||
// 加载img时才执行滚动监听判断是否可加载
|
||||
if (that.loadImg || that.isLoadError) return;
|
||||
const id = that.uid
|
||||
const query = uni.createSelectorQuery().in(that);
|
||||
query.select('#' + id).boundingClientRect(data => {
|
||||
if (!data) return;
|
||||
if (data.top - that.viewHeight < 0) {
|
||||
that.loadImg = !!that.imageSrc;
|
||||
that.isLoadError = !that.loadImg;
|
||||
}
|
||||
}).exec()
|
||||
}, 200)
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
boxStyle(){
|
||||
return {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
borderRadius: this.borderRadius
|
||||
}
|
||||
},
|
||||
imgStyle(){
|
||||
return {
|
||||
borderRadius: this.borderRadius
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.$nextTick(this.onScroll)
|
||||
},
|
||||
handleBorderLoad() {
|
||||
this.borderLoaded = 1;
|
||||
},
|
||||
handleBorderError() {
|
||||
this.borderLoaded = 2;
|
||||
},
|
||||
handleImgLoad(e) {
|
||||
this.showImg = true;
|
||||
setTimeout(() => {
|
||||
this.showTransition = true
|
||||
}, 50)
|
||||
},
|
||||
handleImgError(e) {
|
||||
this.isLoadError = true;
|
||||
},
|
||||
onScroll() {
|
||||
this.scrollFn();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
uni.$on('scroll', this.scrollFn);
|
||||
this.onScroll()
|
||||
},
|
||||
beforeDestroy() {
|
||||
uni.$off('scroll', this.scrollFn);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.easy-loadimage {
|
||||
position: relative;
|
||||
|
||||
}
|
||||
.border-img {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* max-height: 360rpx; */
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* 官方优化图片tips */
|
||||
image {
|
||||
will-change: transform
|
||||
}
|
||||
|
||||
/* 渐变过渡效果处理 */
|
||||
image.origin-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
image.origin-img.show-transition {
|
||||
transition: opacity .5s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
image.origin-img.no-transition {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 渐变过渡效果处理 */
|
||||
image.border-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.3;
|
||||
/* max-height: 360rpx; */
|
||||
}
|
||||
|
||||
image.border-img.show-transition {
|
||||
transition: opacity .5s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
image.border-img.no-transition {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 加载失败、加载中的占位图样式控制 */
|
||||
.loadfail-img {
|
||||
height: 100%;
|
||||
background: url('~@/static/easy-loadimage/loadfail.png') no-repeat center;
|
||||
background-size: 50%;
|
||||
}
|
||||
|
||||
.loading-img {
|
||||
height: 100%;
|
||||
}
|
||||
.widthFix{
|
||||
min-height: 172px;
|
||||
}
|
||||
|
||||
/* 转圈 */
|
||||
.spin-circle {
|
||||
background: url('~@/static/easy-loadimage/loading.png') no-repeat center;
|
||||
background-size: 60%;
|
||||
}
|
||||
|
||||
/* 动态灰色若隐若现 */
|
||||
.looming-gray {
|
||||
animation: looming-gray 1s infinite linear;
|
||||
background-color: #e3e3e3;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
@keyframes looming-gray {
|
||||
0% {
|
||||
background-color: #e3e3e3aa;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: #e3e3e3;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: #e3e3e3aa;
|
||||
}
|
||||
}
|
||||
|
||||
/* 骨架屏1 */
|
||||
.skeleton-1 {
|
||||
background-color: #e3e3e3;
|
||||
background-image: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 80%);
|
||||
background-size: 100rpx 100%;
|
||||
background-repeat: repeat-y;
|
||||
background-position: 0 0;
|
||||
animation: skeleton-1 .6s infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-1 {
|
||||
to {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 骨架屏2 */
|
||||
.skeleton-2 {
|
||||
background-image: linear-gradient(-90deg, #fefefe 0%, #e6e6e6 50%, #fefefe 100%);
|
||||
background-size: 400% 400%;
|
||||
background-position: 0 0;
|
||||
animation: skeleton-2 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-2 {
|
||||
to {
|
||||
background-position: -135% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
319
components/eidtUserModal/index.vue
Normal file
319
components/eidtUserModal/index.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<view :style="colorStyle">
|
||||
<view class="product-window" :class="{'on':isShow}">
|
||||
<view class="iconfont icon-guanbi" @click="closeAttr"></view>
|
||||
<view class="mp-data">
|
||||
<image :src="mpData.site_logo" mode=""></image>
|
||||
<text class="mp-name">{{mpData.site_name || ''}} 申请</text>
|
||||
</view>
|
||||
<view class="trip-msg">
|
||||
<view class="title">
|
||||
获取您的昵称、头像
|
||||
</view>
|
||||
<view class="trip">
|
||||
提供具有辨识度的用户中心界面
|
||||
</view>
|
||||
</view>
|
||||
<form @submit="formSubmit">
|
||||
<view class="edit">
|
||||
<view class="avatar edit-box">
|
||||
<view class="left">
|
||||
<view class="head">头像</view>
|
||||
<!-- <image :src="userInfo.avatar || defaultAvatar" mode=""></image> -->
|
||||
<view class="avatar-box" v-if="!mp_is_new" @click.stop='uploadpic'>
|
||||
<image :src="userInfo.avatar || defHead"></image>
|
||||
</view>
|
||||
<button v-else class="avatar-box" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
|
||||
<image :src="userInfo.avatar || defHead"></image>
|
||||
</button>
|
||||
</view>
|
||||
<!-- <view class="iconfont icon-xiangyou"></view> -->
|
||||
</view>
|
||||
<view class="nickname edit-box">
|
||||
<view class="left">
|
||||
<view class="head">昵称</view>
|
||||
<view class='input'><input type='nickname' :always-embed="true" :adjust-position="true" cursor-spacing="30" placeholder-class="pl-sty"
|
||||
placeholder="请输入昵称" name='nickname' :maxlength="16"
|
||||
:value='userInfo.nickname'></input>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="iconfont icon-xiangyou"></view> -->
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="bottom">
|
||||
<button class="save" formType="submit" :class="{'open': userInfo.avatar}">
|
||||
保存
|
||||
</button>
|
||||
</view>
|
||||
</form>
|
||||
</view>
|
||||
<canvas canvas-id="canvas" v-if="canvasStatus"
|
||||
:style="{width: canvasWidth + 'px', height: canvasHeight + 'px',position: 'absolute',left:'-100000px',top:'-100000px'}"></canvas>
|
||||
<view class="mask" @touchmove.prevent v-if="isShow" @click="closeAttr"></view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import colors from "@/mixins/color";
|
||||
import Cache from '@/utils/cache';
|
||||
import {
|
||||
userEdit,
|
||||
} from '@/api/user.js';
|
||||
import {
|
||||
copyRight
|
||||
} from '@/api/api.js';
|
||||
export default {
|
||||
mixins: [colors],
|
||||
props: {
|
||||
isShow: {
|
||||
type: Number,
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defHead: require('@/static/images/f.png'),
|
||||
mp_is_new: this.$Cache.get('MP_VERSION_ISNEW') || false,
|
||||
userInfo: {
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
},
|
||||
mpData: {
|
||||
site_logo: '',
|
||||
site_name: ''
|
||||
},
|
||||
canvasStatus: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
let configData = this.$Cache.get('BASIC_CONFIG');
|
||||
this.mpData.site_logo = configData.logo_url;
|
||||
this.mpData.site_name = configData.site_name;
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
*/
|
||||
uploadpic: function() {
|
||||
let that = this;
|
||||
this.canvasStatus = true
|
||||
that.$util.uploadImageChange('upload/image', (res) => {
|
||||
let userInfo = that.userInfo;
|
||||
if (userInfo !== undefined) {
|
||||
that.userInfo.avatar = res.data.url;
|
||||
}
|
||||
this.canvasStatus = false
|
||||
}, (res) => {
|
||||
this.canvasStatus = false
|
||||
}, (res) => {
|
||||
this.canvasWidth = res.w
|
||||
this.canvasHeight = res.h
|
||||
});
|
||||
},
|
||||
// 微信头像获取
|
||||
onChooseAvatar(e) {
|
||||
const {
|
||||
avatarUrl
|
||||
} = e.detail
|
||||
this.$util.uploadImgs('upload/image', avatarUrl, (res) => {
|
||||
this.userInfo.avatar = res.data.url
|
||||
}, (err) => {
|
||||
console.log(err)
|
||||
})
|
||||
},
|
||||
closeAttr: function() {
|
||||
this.$emit('closeEdit');
|
||||
},
|
||||
/**
|
||||
* 提交修改
|
||||
*/
|
||||
formSubmit(e) {
|
||||
let that = this
|
||||
if (!this.userInfo.avatar) return that.$util.Tips({
|
||||
title: '请上传头像'
|
||||
});
|
||||
if (!e.detail.value.nickname) return that.$util.Tips({
|
||||
title: '请输入昵称'
|
||||
});
|
||||
this.userInfo.nickname = e.detail.value.nickname
|
||||
userEdit(this.userInfo).then(res => {
|
||||
this.$emit('editSuccess')
|
||||
return that.$util.Tips({
|
||||
title: res.msg,
|
||||
icon: 'success'
|
||||
},{
|
||||
tab: 3
|
||||
});
|
||||
}).catch(msg => {
|
||||
return that.$util.Tips({
|
||||
title: msg || '保存失败'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.pl-sty {
|
||||
color: #999999;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
.product-window.on {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.mask {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.product-window {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
z-index: 1000;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transition: all .3s cubic-bezier(.25, .5, .5, .9);
|
||||
padding: 38rpx 40rpx;
|
||||
padding-bottom: 90rpx;
|
||||
padding-bottom: calc(90rpx + constant(safe-area-inset-bottom)); ///兼容 IOS<11.2/
|
||||
padding-bottom: calc(90rpx + env(safe-area-inset-bottom)); ///兼容 IOS>11.2/
|
||||
|
||||
.icon-guanbi {
|
||||
position: absolute;
|
||||
top: 30rpx;
|
||||
right: 30rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.mp-data {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.mp-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.trip-msg {
|
||||
padding-bottom: 32rpx;
|
||||
border-bottom: 1px solid #F5F5F5;
|
||||
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.trip {
|
||||
font-size: 26rpx;
|
||||
color: #777777;
|
||||
}
|
||||
}
|
||||
|
||||
.edit {
|
||||
border-bottom: 1px solid #F5F5F5;
|
||||
|
||||
.avatar {
|
||||
border-bottom: 1px solid #F5F5F5;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
.input {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
input {
|
||||
height: 80rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
padding: 22rpx 0;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
.head {
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
white-space: nowrap;
|
||||
margin-right: 60rpx;
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
image {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-xiangyou {
|
||||
color: #cfcfcf;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.save {
|
||||
border: 1px solid #F5F5F5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 368rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-top: 52rpx;
|
||||
background-color: #F5F5F5;
|
||||
color: #ccc;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.save.open {
|
||||
border: 1px solid #fff;
|
||||
background-color: var(--view-theme);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
61
components/emptyPage.vue
Normal file
61
components/emptyPage.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<!-- 无数据时显示 -->
|
||||
<view class="empty-page w-full flex-col flex-center pt-82 bg--w111-fff rd-24rpx">
|
||||
<image class="empty-img" :src="imgSrc"></image>
|
||||
<view v-if="!isLogin" class="fs-28 fw-500 text--w111-282828">暂未登录</view>
|
||||
<view class="fs-26 text--w111-999 lh-36rpx pt-16">{{title}}</view>
|
||||
<view class="w-360 h-72 rd-36rpx flex-center font-num fw-500 fs-26 theme-border mt-48"
|
||||
v-if="!isLogin" @tap="goLog">立即登录</view>
|
||||
<slot name="bottom"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {HTTP_REQUEST_URL} from '@/config/app';
|
||||
import { toLogin } from '@/libs/login.js';
|
||||
export default{
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '暂无记录',
|
||||
},
|
||||
src:{
|
||||
type: String,
|
||||
default: '/statics/images/empty-box.gif',
|
||||
},
|
||||
isLogin:{
|
||||
type:Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
imgHost:HTTP_REQUEST_URL
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
imgSrc(){
|
||||
return HTTP_REQUEST_URL + this.src
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
goLog(){
|
||||
toLogin()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.pt-82{
|
||||
padding: 82rpx 0 160rpx;
|
||||
}
|
||||
.empty-img{
|
||||
width: 440rpx;
|
||||
height: 360rpx;
|
||||
}
|
||||
.theme-border{
|
||||
border: 1px solid var(--view-theme);
|
||||
}
|
||||
</style>
|
||||
185
components/ewcomerPop/index.vue
Normal file
185
components/ewcomerPop/index.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<view :style="colorStyle">
|
||||
<image :src="imgHost+'/statics/images/header.png'" mode="" class="img"></image>
|
||||
<view class="tipaddress">
|
||||
<view class="title">{{fromActive == 1?'激活送好礼':'新人大礼包'}}</view>
|
||||
<view class="list">
|
||||
<view class="list-img acea-row row-between-wrapper" :style="{backgroundImage:'url('+imgHost+'/statics/images/box1.png'+')'}" v-if="comerGift.product_count>0 && fromActive == 0">
|
||||
<view class="left acea-row row-center-wrapper">
|
||||
<image :src="imgHost+'/statics/images/vip.png'" mode=""></image>
|
||||
</view>
|
||||
<view class="right">新人专享价商品</view>
|
||||
</view>
|
||||
<view class="list-img acea-row row-between-wrapper" :style="{backgroundImage:'url('+imgHost+'/statics/images/box1.png'+')'}" v-if="comerGift.first_order_discount>0 && fromActive == 0">
|
||||
<view class="left">
|
||||
{{parseFloat(comerGift.first_order_discount)/10 || 10}}<text class="text">折</text>
|
||||
</view>
|
||||
<view class="right">新人首单优惠</view>
|
||||
</view>
|
||||
<view class="list-img acea-row row-between-wrapper" :style="{backgroundImage:'url('+imgHost+'/statics/images/box1.png'+')'}" v-if="comerGift.register_give_integral>0">
|
||||
<view class="left">{{comerGift.register_give_integral}}</view>
|
||||
<view class="right">新人赠送积分</view>
|
||||
</view>
|
||||
<view class="list-img acea-row row-between-wrapper" :style="{backgroundImage:'url('+imgHost+'/statics/images/box1.png'+')'}" v-if="comerGift.register_give_money>0">
|
||||
<view class="left">{{comerGift.register_give_money}}<text class="text">元</text></view>
|
||||
<view class="right">新人赠送余额</view>
|
||||
</view>
|
||||
<view class="list-img acea-row row-between-wrapper" :style="{backgroundImage:'url('+imgHost+'/statics/images/box1.png'+')'}" v-if="comerGift.coupon_count>0" v-for="(item,index) in comerGift.register_give_coupon" :key="index">
|
||||
<view class="left">
|
||||
<text v-if="item.coupon_type==1">{{item.coupon_price.toString().split(".")[0]}}</text>
|
||||
<text class="nums"
|
||||
v-if="item.coupon_price.toString().split('.').length>1 && item.coupon_type==1">.{{item.coupon_price.toString().split(".")[1]}}</text>
|
||||
<text v-if="item.coupon_type==2">{{parseFloat(item.coupon_price)/10}}</text>
|
||||
<text class="text">{{item.coupon_type==1?'元':'折'}}</text>
|
||||
</view>
|
||||
<view class="right">优惠券</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn" @click="accept">
|
||||
立即收下
|
||||
</view>
|
||||
</view>
|
||||
<view class="mark"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
HTTP_REQUEST_URL
|
||||
} from '@/config/app';
|
||||
import colors from '@/mixins/color';
|
||||
export default {
|
||||
mixins: [colors],
|
||||
props:{
|
||||
comerGift: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
fromActive: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imgHost: HTTP_REQUEST_URL,
|
||||
};
|
||||
},
|
||||
methods:{
|
||||
accept(){
|
||||
this.$emit('comerPop')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.img {
|
||||
position: fixed;
|
||||
top: 162rpx;
|
||||
left: 9%;
|
||||
width: 590rpx;
|
||||
height: 294rpx;
|
||||
z-index: 100;
|
||||
|
||||
}
|
||||
|
||||
.tipaddress {
|
||||
position: fixed;
|
||||
left: 13%;
|
||||
top: 25%;
|
||||
width: 538rpx;
|
||||
height: 650rpx;
|
||||
background-color: var(--view-theme);
|
||||
border-radius: 10rpx;
|
||||
z-index: 100;
|
||||
text-align: center;
|
||||
|
||||
.title{
|
||||
color: #fff;
|
||||
font-size: 50rpx;
|
||||
margin-top: 32rpx;
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
width: 258rpx;
|
||||
height: 52rpx;
|
||||
margin-top: 50rpx;
|
||||
|
||||
}
|
||||
|
||||
.list {
|
||||
height: 370rpx;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
.list-img {
|
||||
margin-top: 14rpx;
|
||||
margin-left: 32rpx;
|
||||
width: 474rpx;
|
||||
height: 124rpx;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
|
||||
.left {
|
||||
width: 144rpx;
|
||||
font-size: 48rpx;
|
||||
font-weight: 500;
|
||||
color: var(--view-theme);
|
||||
|
||||
image{
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.nums{
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 328rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: var(--view-theme);
|
||||
text-align: left;
|
||||
padding-left: 50rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.btn {
|
||||
width: 474rpx;
|
||||
height: 78rpx;
|
||||
background: #FFDE5C;
|
||||
border-radius: 39rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #B66A08;
|
||||
line-height: 78rpx;
|
||||
text-align: center;
|
||||
margin-left: 32rpx;
|
||||
margin-top: 48rpx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.mark {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 99;
|
||||
}
|
||||
</style>
|
||||
145
components/guide/index.vue
Normal file
145
components/guide/index.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<!-- 开屏广告 -->
|
||||
<view class="content">
|
||||
<swiper class="swiper" :class="advData.value.length==1?'on':''" :autoplay="autoplay" :duration="duration" @change="stopChange"
|
||||
v-if="advData.type == 'pic' && advData.value.length">
|
||||
<swiper-item v-for="(item,index) in advData.value" :key="index" @click="jump(item.link)">
|
||||
<view class="swiper-item">
|
||||
<view class="swiper-item-img">
|
||||
<image :src="item.img" mode="aspectFill"></image>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view class="video-box" v-else-if="advData.type == 'video' && advData.video_link">
|
||||
<video class="vid" :src="advData.video_link" :autoplay="true" :loop="true" :muted="true"
|
||||
:controls="false"></video>
|
||||
</view>
|
||||
<view class="jump-over" :style="{ top: navH + 'rpx' }" @tap="launchFlag()">跳过<text v-if="closeType == 1">{{time}}</text><slot name="bottom"></slot></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let app = getApp();
|
||||
let sysHeight = uni.getWindowInfo().statusBarHeight;
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
autoplay: false,
|
||||
duration: 500,
|
||||
jumpover: '跳过',
|
||||
experience: '立即体验',
|
||||
time: this.advData.time,
|
||||
timecount: undefined,
|
||||
navH: 100 + sysHeight * 2,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
advData: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
// 1 倒计时 2 手动关闭(预留)
|
||||
closeType: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.timer()
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timecount)
|
||||
},
|
||||
methods: {
|
||||
stopChange(){
|
||||
if(this.advData.value.length == 1){
|
||||
return false
|
||||
}
|
||||
},
|
||||
timer() {
|
||||
var t = this.advData.time || 5
|
||||
this.timecount = setInterval(() => {
|
||||
t--
|
||||
this.time = t
|
||||
if (t <= 0) {
|
||||
clearInterval(this.timecount)
|
||||
this.launchFlag()
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
launchFlag() {
|
||||
clearInterval(this.timecount)
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
});
|
||||
},
|
||||
jump(url) {
|
||||
if(url){
|
||||
clearInterval(this.timecount)
|
||||
this.$util.JumpPath(url);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
page,
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: 100% auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.swiper-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
/* justify-content: center; */
|
||||
align-items: flex-end;
|
||||
flex-direction: column-reverse
|
||||
}
|
||||
|
||||
.swiper-item-img {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.swiper-item-img image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.jump-over {
|
||||
position: absolute;
|
||||
height: 45rpx;
|
||||
line-height: 45rpx;
|
||||
padding: 0 15rpx;
|
||||
border-radius: 30rpx;
|
||||
font-size: 24rpx;
|
||||
color: #b09e9a;
|
||||
border: 1px solid #b09e9a;
|
||||
z-index: 999;
|
||||
right: 30rpx;
|
||||
}
|
||||
|
||||
.video-box {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
.vid {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
511
components/home/index.vue
Normal file
511
components/home/index.vue
Normal file
@@ -0,0 +1,511 @@
|
||||
<template>
|
||||
<!-- 悬浮导航按钮 -->
|
||||
<view :style="colorStyle" v-if="is_show && showMenu">
|
||||
<view style="touch-action: none">
|
||||
<view class="home" style="position: fixed" :style="{ top: top + 'px' }" id="right-nav"
|
||||
@touchmove.stop.prevent="setTouchMove">
|
||||
<view class="menus" :class="isOpen ? `menus_isOpen-${menus.length}` : ''" v-if="index == 3 ">
|
||||
<view id="fab3"></view>
|
||||
<!-- style="height: 298rpx;" -->
|
||||
<view class="menu menu_main" @click="changeStatus">
|
||||
<image :src="isOpen ? main_after_image : main_ago_image" class="image"
|
||||
style="border-radius: 50%;" />
|
||||
</view>
|
||||
|
||||
<view v-for="(menu, index) in menus" :key="index"
|
||||
:class="['menu menu_a', `menu_a--${index + 1}`]" @click.stop="jump(menu.url)">
|
||||
<image :src="menu.img" class="icon-img"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view id="fab1" class="fab1 p-10" v-if="index == 1" @touchmove.stop.prevent="setTouchMove">
|
||||
<view v-show="isOpen" class="img-list">
|
||||
<view v-for="(item, index) in menus" :key="index" class="img-box" @click="jump(item.url)">
|
||||
<image class="img" :src="item.img" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-box" @click="changeStatus">
|
||||
<view class="img-box">
|
||||
<image class="img" :src="main_ago_image" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view id="fab2" class="fab2 flex-y-center relative p-10" :class="{ hide: isHide }" v-if="index == 2">
|
||||
<view v-if="isOpen" class="img-list">
|
||||
<view v-for="(item, index) in menus" :key="index" class="img-box" @click="jump(item.url)">
|
||||
<image class="img" :src="item.img" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!isOpen" class="btn-box" @click="changeStatus">
|
||||
<view class="img-box">
|
||||
<image class="img" :src="main_ago_image" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isOpen" class="icon" @click="changeStatus">
|
||||
<text class="iconfont icon-ic_rightarrow"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="menus fab4" :class="isOpen ? `menus_isOpen menus_isOpen-${menus.length}` : ''"
|
||||
v-if="index == 4">
|
||||
<view id="fab4" style="height: 350rpx;width: 1px;"></view>
|
||||
<view class="menu_bag" :class="isOpen ? ' has-bag' : opend ? 'close-bag' : ''"></view>
|
||||
<view class="menu menu_main menu_main4" @click="changeStatus">
|
||||
<text v-if="isOpen" class="iconfont icon-ic_close"></text>
|
||||
</view>
|
||||
|
||||
<view v-for="(menu, index) in menus" :key="index"
|
||||
:style="{ zIndex: isOpen ? 9999 : '-1' }"
|
||||
:class="['menu menu_a', `menu_a--${index + 1}`]" @click.stop="jump(menu.url)">
|
||||
<image :src="menu.img" class="icon-img"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import {mapGetters} from 'vuex';
|
||||
import colors from '@/mixins/color.js';
|
||||
import {HTTP_REQUEST_URL} from '@/config/app';
|
||||
import Vue from 'vue';
|
||||
import {getSuspended} from '@/api/api.js';
|
||||
export default {
|
||||
name: 'fabHome',
|
||||
props: {
|
||||
// 风格样式
|
||||
styleType: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
},
|
||||
mixins: [colors],
|
||||
data: function() {
|
||||
return {
|
||||
top: '545',
|
||||
imgHost: HTTP_REQUEST_URL,
|
||||
isOpen: false, //菜单是否展开
|
||||
opend: false, //是否打开过
|
||||
menus: [],
|
||||
main_image: '',
|
||||
shifting: 10,
|
||||
index: 4,
|
||||
is_show: 1,
|
||||
main_after_image: '',
|
||||
main_ago_image: '',
|
||||
windowHeight: 0,
|
||||
fabHeight: 0,
|
||||
isHide: false,
|
||||
menu:{
|
||||
color:'rgba(0, 0, 0, 0.10)'
|
||||
},
|
||||
showMenu: true
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
...mapGetters(['isLogin'])
|
||||
},
|
||||
mounted() {
|
||||
uni.$on('scroll', this.scrollFn);
|
||||
if(this.isLogin && [1,2].includes(this.$store.state.app.identity)){
|
||||
this.showMenu = false;
|
||||
}else{
|
||||
setTimeout(()=>{
|
||||
this.getSuspended();
|
||||
},500)
|
||||
}
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
this.windowHeight = res.windowHeight;
|
||||
}
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$Cache.set('homeTop',this.top);
|
||||
},
|
||||
methods: {
|
||||
changeStatus() {
|
||||
this.isOpen = !this.isOpen;
|
||||
this.opend = true;
|
||||
this.isHide = false;
|
||||
},
|
||||
setTouchMove(e) {
|
||||
let that = this;
|
||||
let clientY = e.touches[0].clientY;
|
||||
if (clientY > (that.fabHeight / 2) && clientY < (that.windowHeight - that.fabHeight / 2)) {
|
||||
that.top = clientY;
|
||||
}
|
||||
},
|
||||
scrollFn() {
|
||||
this.isOpen = false;
|
||||
this.isHide = true;
|
||||
},
|
||||
jump(url) {
|
||||
this.$util.JumpPath(url);
|
||||
},
|
||||
// 获取配置信息
|
||||
getSuspended() {
|
||||
getSuspended().then(res => {
|
||||
const data = res.data;
|
||||
this.menus = data.button;
|
||||
this.index = data.index;
|
||||
this.is_show = data.is_show;
|
||||
this.main_image = data.main_image;
|
||||
this.main_after_image = data.main_after_image || '';
|
||||
this.main_ago_image = data.main_ago_image || '';
|
||||
this.shifting = data.shifting;
|
||||
if(data.is_show){
|
||||
this.$nextTick(() => {
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query
|
||||
.select(`#fab${this.index}`)
|
||||
.boundingClientRect((data) => {
|
||||
this.fabHeight = data.height;
|
||||
if(this.$Cache.get('homeTop')){
|
||||
this.top = this.$Cache.get('homeTop')
|
||||
}else{
|
||||
if (this.shifting == 100) {
|
||||
this.top = this.windowHeight - data.height / 2
|
||||
} else if (this.shifting == 0) {
|
||||
this.top = data.height / 2
|
||||
} else {
|
||||
this.top = this.shifting / 100 * this.windowHeight
|
||||
}
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.home {
|
||||
position: fixed;
|
||||
color: white;
|
||||
text-align: center;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.menus {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
position: relative;
|
||||
right: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.menus .menu {
|
||||
display: block;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition-timing-function: cubic-bezier(0.25, 1.2, 0.45, 1);
|
||||
transition-duration: 1s;
|
||||
}
|
||||
|
||||
.menu_bag {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
// border-radius: 90rpx 0 0 90rpx;
|
||||
border-radius: 70rpx;
|
||||
background: rgba(51, 51, 51, 0.1);
|
||||
box-shadow: 0rpx 0rpx 17rpx 0rpx rgba(0, 0, 0, 0.1);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.close-bag {
|
||||
animation: close 0.5s ease forwards;
|
||||
}
|
||||
|
||||
.has-bag {
|
||||
border-radius: 50%;
|
||||
animation: show 0.4s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes show {
|
||||
0% {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
|
||||
100% {
|
||||
width: 350rpx;
|
||||
height: 350rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes close {
|
||||
0% {
|
||||
width: 350rpx;
|
||||
height: 350rpx;
|
||||
}
|
||||
|
||||
100% {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.menus .menu_main {
|
||||
z-index: 2;
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
font-size: 36px;
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.menus .menu_a {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
color: inherit;
|
||||
// box-shadow: 0 0 6rpx var(--color);
|
||||
// transform: translate(-50%, -50%) rotate(180deg);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/* 点击悬浮球主菜单按钮后样式 */
|
||||
.menus_isOpen .menu_main {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
line-height: 70rpx;
|
||||
font-size: 28rpx;
|
||||
color: #607d88;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.menus_isOpen-3 {
|
||||
.menu_a--1 {
|
||||
transform: translate(-100%, calc(-50% - 93rpx));
|
||||
}
|
||||
|
||||
.menu_a--2 {
|
||||
transform: translate(calc(-50% - 100rpx), calc(-50%));
|
||||
}
|
||||
|
||||
.menu_a--3 {
|
||||
transform: translate(-100%, calc(-50% + 93rpx));
|
||||
}
|
||||
}
|
||||
|
||||
.menus_isOpen-4 {
|
||||
.menu_a--1 {
|
||||
transform: translate(-50%, calc(-50% - 100rpx));
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.menu_a--2 {
|
||||
transform: translate(calc(-50% - 90rpx), calc(-50% - 50rpx));
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.menu_a--3 {
|
||||
transform: translate(calc(-50% - 90rpx), calc(-50% + 50rpx));
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.menu_a--4 {
|
||||
transform: translate(-50%, calc(-50% + 100rpx));
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.menu_a--5 {
|
||||
transform: translate(calc(-50% + 100rpx), calc(-50% + 50rpx));
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.menu_a--6 {
|
||||
transform: translate(calc(-50% + 100rpx), calc(-50% - 50rpx));
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.menus_isOpen-5 {
|
||||
.menu_a--1 {
|
||||
transform: translate(-50%, calc(-50% - 110rpx));
|
||||
}
|
||||
|
||||
.menu_a--2 {
|
||||
transform: translate(calc(-50% - 76rpx), calc(-50% - 76rpx));
|
||||
}
|
||||
|
||||
.menu_a--3 {
|
||||
transform: translate(calc(-50% - 106rpx), -50%);
|
||||
}
|
||||
|
||||
.menu_a--4 {
|
||||
transform: translate(calc(-50% - 76rpx), calc(-50% + 76rpx));
|
||||
}
|
||||
|
||||
.menu_a--5 {
|
||||
transform: translate(-50%, calc(-50% + 110rpx));
|
||||
}
|
||||
}
|
||||
|
||||
.icon-img {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.fab1 {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
bottom: 0;
|
||||
border-radius: 45rpx;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
backdrop-filter: blur(10rpx);
|
||||
|
||||
|
||||
.img-box {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 50%;
|
||||
background: #FFFFFF;
|
||||
|
||||
.img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.img-list {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
.img-box {
|
||||
margin: 0 0 16rpx 0;
|
||||
box-shadow: 0rpx 0rpx 16rpx 0rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-box {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
.img-box {}
|
||||
}
|
||||
}
|
||||
|
||||
.fab2 {
|
||||
border-radius: 45rpx 0 0 45rpx;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
backdrop-filter: blur(10rpx);
|
||||
|
||||
&.hide {
|
||||
transform: translateX(42rpx);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.img-box {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 50%;
|
||||
background: #FFFFFF;
|
||||
|
||||
.img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.img-list {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
|
||||
.img-box {
|
||||
box-shadow: 0rpx 0rpx 16rpx 0rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
+.img-box {
|
||||
margin: 0 0 0 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.menu_main4 {
|
||||
background: #FFFFFF;
|
||||
|
||||
.icon-ic_close {
|
||||
font-size: 34rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.fab4 {
|
||||
.menu_bag {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(51, 51, 51, 0.1);
|
||||
backdrop-filter: blur(7.78rpx);
|
||||
box-shadow: 0rpx 0rpx 13rpx 0rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.close-bag {
|
||||
background: rgba(51, 51, 51, 0.1);
|
||||
backdrop-filter: blur(7.78rpx);
|
||||
}
|
||||
|
||||
.has-bag {
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.menu_main {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
&.menus_isOpen {
|
||||
.menu_main {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
133
components/homeList/index.vue
Normal file
133
components/homeList/index.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<!-- 顶部下拉导航 -->
|
||||
<view class="dialog_nav dialogIndex"
|
||||
:style="{ top: navH + 'px' }" v-show="currentPage">
|
||||
<view class="dialog_nav_item" :class="item.after"
|
||||
v-for="(item,index) in headerNav" :key="index"
|
||||
@tap="linkPage(item.url)">
|
||||
<text class="iconfont" :class="item.icon"></text>
|
||||
<text class="pl-20">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "homeIdex",
|
||||
props: {
|
||||
navH: {
|
||||
type: String|Number,
|
||||
default: ""
|
||||
},
|
||||
currentPage: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
openNavList:{
|
||||
type: Array,
|
||||
default: ()=>[]
|
||||
},
|
||||
navList: {
|
||||
type: Array,
|
||||
default: ()=>[]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectNavList:[
|
||||
{name:'首页',icon:'icon-ic_mall',url:'/pages/index/index',after:'dialog_after'},
|
||||
{name:'搜索',icon:'icon-ic_search',url:'/pages/goods/goods_search/index',after:'dialog_after'},
|
||||
{name:'购物车',icon:'icon-ic_ShoppingCart1',url:'/pages/order_addcart/order_addcart',after:'dialog_after'},
|
||||
{name:'我的收藏',icon:'icon-ic_star',url:'/pages/users/user_goods_collection/index',after:'dialog_after'},
|
||||
{name:'个人中心',icon:'icon-a-ic_user1',url:'/pages/user/index'},
|
||||
]
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
headerNav(){
|
||||
if(this.navList.length){
|
||||
return this.navList
|
||||
}else{
|
||||
let arr = [];
|
||||
this.selectNavList.forEach((item,index)=>{
|
||||
if(this.openNavList.includes(index)){
|
||||
arr.push(item);
|
||||
}
|
||||
})
|
||||
return arr
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
linkPage(url){
|
||||
if (['/pages/goods_cate/goods_cate', '/pages/order_addcart/order_addcart', '/pages/user/index', '/pages/index/index']
|
||||
.indexOf(url) == -1) {
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
})
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url: url
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dialog_nav{
|
||||
position: fixed;
|
||||
left: 14rpx;
|
||||
width: 240rpx;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 0px 16rpx rgba(0, 0, 0, 0.08);
|
||||
z-index: 310;
|
||||
border-radius: 14rpx;
|
||||
&::before{
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
left: -26rpx;
|
||||
right: 0;
|
||||
margin:auto;
|
||||
top:-9px;
|
||||
border-bottom: 10px solid #FFFFFF;
|
||||
border-left: 10px solid transparent; /*transparent 表示透明*/
|
||||
border-right: 10px solid transparent;
|
||||
}
|
||||
&.dialogIndex{
|
||||
left: 14rpx;
|
||||
&::before{
|
||||
left: 0rpx !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dialog_nav_item{
|
||||
width: 100%;
|
||||
height: 84rpx;
|
||||
line-height: 84rpx;
|
||||
padding: 0 20rpx 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom: #eee;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
position: relative;
|
||||
display: flex;
|
||||
.iconfont{
|
||||
font-size: 32rpx;
|
||||
margin-right: 26rpx;
|
||||
}
|
||||
}
|
||||
.dialog_after{
|
||||
::after{
|
||||
content: '';
|
||||
position: absolute;
|
||||
width:90px;
|
||||
height: 1px;
|
||||
background-color: #EEEEEE;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
814
components/jyf-parser/jyf-parser.vue
Normal file
814
components/jyf-parser/jyf-parser.vue
Normal file
@@ -0,0 +1,814 @@
|
||||
<!--
|
||||
parser 主模块组件
|
||||
github:https://github.com/jin-yufeng/Parser
|
||||
docs:https://jin-yufeng.github.io/Parser
|
||||
插件市场:https://ext.dcloud.net.cn/plugin?id=805
|
||||
author:JinYufeng
|
||||
update:2020/04/14
|
||||
-->
|
||||
<template>
|
||||
<view>
|
||||
<slot v-if="!nodes.length" />
|
||||
<!--#ifdef APP-PLUS-NVUE-->
|
||||
<web-view id="top" ref="web" :src="src" :style="'margin-top:-2px;height:'+height+'px'" @onPostMessage="_message" />
|
||||
<!--#endif-->
|
||||
<!--#ifndef APP-PLUS-NVUE-->
|
||||
<view id="top" :style="showAm+(selectable?';user-select:text;-webkit-user-select:text':'')" :animation="scaleAm" @tap="_tap"
|
||||
@touchstart="_touchstart" @touchmove="_touchmove">
|
||||
<!--#ifdef H5-->
|
||||
<div :id="'rtf'+uid"></div>
|
||||
<!--#endif-->
|
||||
<!--#ifndef H5-->
|
||||
<trees :nodes="nodes" :lazy-load="lazyLoad" :loadVideo="loadVideo" />
|
||||
<image v-for="(item, index) in imgs" v-bind:key="index" :id="index" :src="item" hidden @load="_load" />
|
||||
<!--#endif-->
|
||||
</view>
|
||||
<!--#endif-->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifndef H5 || APP-PLUS-NVUE
|
||||
import trees from './libs/trees';
|
||||
var cache = {},
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
fs = uni.getFileSystemManager ? uni.getFileSystemManager() : null,
|
||||
// #endif
|
||||
Parser = require('./libs/MpHtmlParser.js');
|
||||
var document; // document 补丁包 https://jin-yufeng.github.io/Parser/#/instructions?id=document
|
||||
// 计算 cache 的 key
|
||||
function hash(str) {
|
||||
for (var i = str.length, val = 5381; i--;)
|
||||
val += (val << 5) + str.charCodeAt(i);
|
||||
return val;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef H5 || APP-PLUS-NVUE
|
||||
var rpx = uni.getWindowInfo().screenWidth / 750,
|
||||
cfg = require('./libs/config.js');
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
var dom = weex.requireModule('dom');
|
||||
// #endif
|
||||
export default {
|
||||
name: 'parser',
|
||||
data() {
|
||||
return {
|
||||
// #ifdef APP-PLUS
|
||||
loadVideo: false,
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
uid: this._uid,
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
src: '',
|
||||
height: 1,
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
scaleAm: '',
|
||||
showAm: '',
|
||||
imgs: [],
|
||||
// #endif
|
||||
nodes: []
|
||||
}
|
||||
},
|
||||
// #ifndef H5 || APP-PLUS-NVUE
|
||||
components: {
|
||||
trees
|
||||
},
|
||||
// #endif
|
||||
props: {
|
||||
'html': null,
|
||||
// #ifndef MP-ALIPAY
|
||||
'autopause': {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// #endif
|
||||
'autosetTitle': {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// #ifndef H5 || APP-PLUS-NVUE
|
||||
'compress': Number,
|
||||
'useCache': Boolean,
|
||||
'xml': Boolean,
|
||||
// #endif
|
||||
'domain': String,
|
||||
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
|
||||
'gestureZoom': Boolean,
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN || MP-QQ || H5 || APP-PLUS
|
||||
'lazyLoad': Boolean,
|
||||
// #endif
|
||||
'selectable': Boolean,
|
||||
'tagStyle': Object,
|
||||
'showWithAnimation': Boolean,
|
||||
'useAnchor': Boolean
|
||||
},
|
||||
watch: {
|
||||
html(html) {
|
||||
this.setContent(html);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 图片数组
|
||||
this.imgList = [];
|
||||
this.imgList.each = function(f) {
|
||||
for (var i = 0, len = this.length; i < len; i++)
|
||||
this.setItem(i, f(this[i], i, this));
|
||||
}
|
||||
this.imgList.setItem = function(i, src) {
|
||||
if (i == void 0 || !src) return;
|
||||
// #ifndef MP-ALIPAY || APP-PLUS
|
||||
// 去重
|
||||
if (src.indexOf('http') == 0 && this.includes(src)) {
|
||||
var newSrc = '';
|
||||
for (var j = 0, c; c = src[j]; j++) {
|
||||
if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break;
|
||||
newSrc += Math.random() > 0.5 ? c.toUpperCase() : c;
|
||||
}
|
||||
newSrc += src.substr(j);
|
||||
return this[i] = newSrc;
|
||||
}
|
||||
// #endif
|
||||
this[i] = src;
|
||||
// 暂存 data src
|
||||
if (src.includes('data:image')) {
|
||||
var filePath, info = src.match(/data:image\/(\S+?);(\S+?),(.+)/);
|
||||
if (!info) return;
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`;
|
||||
fs && fs.writeFile({
|
||||
filePath,
|
||||
data: info[3],
|
||||
encoding: info[2],
|
||||
success: () => this[i] = filePath
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
filePath = `_doc/parser_tmp/${Date.now()}.${info[1]}`;
|
||||
var bitmap = new plus.nativeObj.Bitmap();
|
||||
bitmap.loadBase64Data(src, () => {
|
||||
bitmap.save(filePath, {}, () => {
|
||||
bitmap.clear()
|
||||
this[i] = filePath;
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
if (this.html) this.setContent(this.html);
|
||||
},
|
||||
beforeDestroy() {
|
||||
// #ifdef H5
|
||||
if (this._observer) this._observer.disconnect();
|
||||
// #endif
|
||||
this.imgList.each(src => {
|
||||
// #ifdef APP-PLUS
|
||||
if (src && src.includes('_doc')) {
|
||||
plus.io.resolveLocalFileSystemURL(src, entry => {
|
||||
entry.remove();
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO
|
||||
if (src && uni.env && src.includes(uni.env.USER_DATA_PATH))
|
||||
fs && fs.unlink({
|
||||
filePath: src
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
clearInterval(this._timer);
|
||||
},
|
||||
methods: {
|
||||
// #ifdef H5 || APP-PLUS-NVUE
|
||||
_Dom2Str(nodes) {
|
||||
var str = '';
|
||||
for (var node of nodes) {
|
||||
if (node.type == 'text')
|
||||
str += node.text;
|
||||
else {
|
||||
str += ('<' + node.name);
|
||||
for (var attr in node.attrs || {})
|
||||
str += (' ' + attr + '="' + node.attrs[attr] + '"');
|
||||
if (!node.children || !node.children.length) str += '>';
|
||||
else str += ('>' + this._Dom2Str(node.children) + '</' + node.name + '>');
|
||||
}
|
||||
}
|
||||
return str;
|
||||
},
|
||||
_handleHtml(html, append) {
|
||||
if (typeof html != 'string') html = this._Dom2Str(html.nodes || html);
|
||||
// 处理 rpx
|
||||
if (html.includes('rpx'))
|
||||
html = html.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * rpx + 'px');
|
||||
if (!append) {
|
||||
// 处理 tag-style 和 userAgentStyles
|
||||
var style = '<style>@keyframes show{0%{opacity:0}100%{opacity:1}}';
|
||||
for (var item in cfg.userAgentStyles)
|
||||
style += `${item}{${cfg.userAgentStyles[item]}}`;
|
||||
for (item in this.tagStyle)
|
||||
style += `${item}{${this.tagStyle[item]}}`;
|
||||
style += '</style>';
|
||||
html = style + html;
|
||||
}
|
||||
return html;
|
||||
},
|
||||
// #endif
|
||||
setContent(html, append) {
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (!html) {
|
||||
this.src = '';
|
||||
this.height = 1;
|
||||
return;
|
||||
}
|
||||
if (append) return;
|
||||
plus.io.resolveLocalFileSystemURL('_doc', entry => {
|
||||
entry.getDirectory('parser_tmp', {
|
||||
create: true
|
||||
}, entry => {
|
||||
var fileName = Date.now() + '.html';
|
||||
entry.getFile(fileName, {
|
||||
create: true
|
||||
}, entry => {
|
||||
entry.createWriter(writer => {
|
||||
writer.onwriteend = () => {
|
||||
this.nodes = [1];
|
||||
this.src = '_doc/parser_tmp/' + fileName;
|
||||
this.$nextTick(function() {
|
||||
entry.remove();
|
||||
})
|
||||
}
|
||||
html =
|
||||
'<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1' +
|
||||
(this.selectable ? '' : ',user-scalable=no') +
|
||||
'"><script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></' +
|
||||
'script><base href="' + this.domain + '">' + this._handleHtml(html) +
|
||||
'<script>"use strict";function post(t){uni.postMessage({data:t})}' +
|
||||
(this.showWithAnimation ? 'document.body.style.animation="show .5s",' : '') +
|
||||
'document.addEventListener("UniAppJSBridgeReady",function(){post({action:"load",text:document.body.innerText});var t=document.getElementsByTagName("title");t.length&&post({action:"getTitle",title:t[0].innerText});for(var e,o=document.getElementsByTagName("img"),n=[],i=0,r=0;e=o[i];i++)e.onerror=function(){post({action:"error",source:"img",target:this})},e.hasAttribute("ignore")||"A"==e.parentElement.nodeName||(e.i=r++,n.push(e.src),e.onclick=function(){post({action:"preview",img:{i:this.i,src:this.src}})});post({action:"getImgList",imgList:n});for(var a,s=document.getElementsByTagName("a"),c=0;a=s[c];c++)a.onclick=function(){var t,e=this.getAttribute("href");if("#"==e[0]){var r=document.getElementById(e.substr(1));r&&(t=r.offsetTop)}return post({action:"linkpress",href:e,offset:t}),!1};;for(var u,m=document.getElementsByTagName("video"),d=0;u=m[d];d++)u.style.maxWidth="100%",u.onerror=function(){post({action:"error",source:"video",target:this})}' +
|
||||
(this.autopause ? ',u.onplay=function(){for(var t,e=0;t=m[e];e++)t!=this&&t.pause()}' : '') +
|
||||
';for(var g,l=document.getElementsByTagName("audio"),p=0;g=l[p];p++)g.onerror=function(){post({action:"error",source:"audio",target:this})};window.onload=function(){post({action:"ready",height:document.body.scrollHeight})}});</' +
|
||||
'script>';
|
||||
writer.write(html);
|
||||
});
|
||||
})
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if (!html) {
|
||||
if (this.rtf && !append) this.rtf.parentNode.removeChild(this.rtf);
|
||||
return;
|
||||
}
|
||||
var div = document.createElement('div');
|
||||
if (!append) {
|
||||
if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
|
||||
this.rtf = div;
|
||||
} else {
|
||||
if (!this.rtf) this.rtf = div;
|
||||
else this.rtf.appendChild(div);
|
||||
}
|
||||
div.innerHTML = this._handleHtml(html, append);
|
||||
for (var styles = this.rtf.getElementsByTagName('style'), i = 0, style; style = styles[i++];) {
|
||||
style.innerHTML = style.innerHTML.replace(/body/g, '#rtf' + this._uid);
|
||||
style.setAttribute('scoped', 'true');
|
||||
}
|
||||
// 懒加载
|
||||
if (!this._observer && this.lazyLoad && IntersectionObserver) {
|
||||
this._observer = new IntersectionObserver(changes => {
|
||||
for (let item, i = 0; item = changes[i++];) {
|
||||
if (item.isIntersecting) {
|
||||
item.target.src = item.target.getAttribute('data-src');
|
||||
item.target.removeAttribute('data-src');
|
||||
this._observer.unobserve(item.target);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
rootMargin: '900px 0px 900px 0px'
|
||||
})
|
||||
}
|
||||
var _ts = this;
|
||||
// 获取标题
|
||||
var title = this.rtf.getElementsByTagName('title');
|
||||
if (title.length && this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: title[0].innerText
|
||||
})
|
||||
// 图片处理
|
||||
this.imgList.length = 0;
|
||||
var imgs = this.rtf.getElementsByTagName('img');
|
||||
for (let i = 0, j = 0, img; img = imgs[i]; i++) {
|
||||
img.style.maxWidth = '100%';
|
||||
var src = img.getAttribute('src');
|
||||
if (this.domain && src) {
|
||||
if (src[0] == '/') {
|
||||
if (src[1] == '/')
|
||||
img.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
|
||||
else img.src = this.domain + src;
|
||||
} else if (!src.includes('://')) img.src = this.domain + '/' + src;
|
||||
}
|
||||
if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
|
||||
img.i = j++;
|
||||
_ts.imgList.push(img.src || img.getAttribute('data-src'));
|
||||
img.onclick = function() {
|
||||
var preview = true;
|
||||
this.ignore = () => preview = false;
|
||||
_ts.$emit('imgtap', this);
|
||||
if (preview) {
|
||||
uni.previewImage({
|
||||
current: this.i,
|
||||
urls: _ts.imgList
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
img.onerror = function() {
|
||||
_ts.$emit('error', {
|
||||
source: 'img',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
if (_ts.lazyLoad && this._observer && img.src && img.i != 0) {
|
||||
img.setAttribute('data-src', img.src);
|
||||
img.removeAttribute('src');
|
||||
this._observer.observe(img);
|
||||
}
|
||||
}
|
||||
// 链接处理
|
||||
var links = this.rtf.getElementsByTagName('a');
|
||||
for (var link of links) {
|
||||
link.onclick = function() {
|
||||
var jump = true,
|
||||
href = this.getAttribute('href');
|
||||
_ts.$emit('linkpress', {
|
||||
href,
|
||||
ignore: () => jump = false
|
||||
});
|
||||
if (jump && href) {
|
||||
if (href[0] == '#') {
|
||||
if (_ts.useAnchor) {
|
||||
_ts.navigateTo({
|
||||
id: href.substr(1)
|
||||
})
|
||||
}
|
||||
} else if (href.indexOf('http') == 0 || href.indexOf('//') == 0)
|
||||
return true;
|
||||
else {
|
||||
uni.navigateTo({
|
||||
url: href
|
||||
})
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 视频处理
|
||||
var videos = this.rtf.getElementsByTagName('video');
|
||||
_ts.videoContexts = videos;
|
||||
for (let video, i = 0; video = videos[i++];) {
|
||||
video.style.maxWidth = '100%';
|
||||
video.onerror = function() {
|
||||
_ts.$emit('error', {
|
||||
source: 'video',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
video.onplay = function() {
|
||||
if (_ts.autopause)
|
||||
for (let item, i = 0; item = _ts.videoContexts[i++];)
|
||||
if (item != this) item.pause();
|
||||
}
|
||||
}
|
||||
// 音频处理
|
||||
var audios = this.rtf.getElementsByTagName('audios');
|
||||
for (var audio of audios)
|
||||
audio.onerror = function() {
|
||||
_ts.$emit('error', {
|
||||
source: 'audio',
|
||||
target: this
|
||||
});
|
||||
}
|
||||
this.document = this.rtf;
|
||||
if (!append) document.getElementById('rtf' + this._uid).appendChild(this.rtf);
|
||||
this.$nextTick(() => {
|
||||
this.nodes = [1];
|
||||
this.$emit('load');
|
||||
})
|
||||
setTimeout(() => this.showAm = '', 500);
|
||||
// #endif
|
||||
// #ifndef H5 || APP-PLUS-NVUE
|
||||
var nodes;
|
||||
if (!html)
|
||||
return this.nodes = [];
|
||||
else if (typeof html == 'string') {
|
||||
let parser = new Parser(html, this);
|
||||
// 缓存读取
|
||||
if (this.useCache) {
|
||||
var hashVal = hash(html);
|
||||
if (cache[hashVal])
|
||||
nodes = cache[hashVal];
|
||||
else {
|
||||
nodes = parser.parse();
|
||||
cache[hashVal] = nodes;
|
||||
}
|
||||
} else nodes = parser.parse();
|
||||
this.$emit('parse', nodes);
|
||||
} else if (Object.prototype.toString.call(html) == '[object Array]') {
|
||||
// 非本插件产生的 array 需要进行一些转换
|
||||
if (html.length && html[0].PoweredBy != 'Parser') {
|
||||
let parser = new Parser(html, this);
|
||||
(function f(ns) {
|
||||
for (var i = 0, n; n = ns[i]; i++) {
|
||||
if (n.type == 'text') continue;
|
||||
n.attrs = n.attrs || {};
|
||||
for (var item in n.attrs)
|
||||
if (typeof n.attrs[item] != 'string') n.attrs[item] = n.attrs[item].toString();
|
||||
parser.matchAttr(n, parser);
|
||||
if (n.children && n.children.length) {
|
||||
parser.STACK.push(n);
|
||||
f(n.children);
|
||||
parser.popNode(parser.STACK.pop());
|
||||
} else n.children = void 0;
|
||||
}
|
||||
})(html);
|
||||
}
|
||||
nodes = html;
|
||||
} else if (typeof html == 'object' && html.nodes) {
|
||||
nodes = html.nodes;
|
||||
console.warn('错误的 html 类型:object 类型已废弃');
|
||||
} else
|
||||
return console.warn('错误的 html 类型:' + typeof html);
|
||||
// #ifdef APP-PLUS
|
||||
this.loadVideo = false;
|
||||
// #endif
|
||||
if (document) this.document = new document(this.nodes, 'nodes', this);
|
||||
if (append) this.nodes = this.nodes.concat(nodes);
|
||||
else this.nodes = nodes;
|
||||
if (nodes.length && nodes[0].title && this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: nodes[0].title
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.imgList.length = 0;
|
||||
this.videoContexts = [];
|
||||
// #ifdef MP-TOUTIAO
|
||||
setTimeout(() => {
|
||||
// #endif
|
||||
var f = (cs) => {
|
||||
for (let i = 0, c; c = cs[i++];) {
|
||||
if (c.$options.name == 'trees') {
|
||||
for (var j = c.nodes.length, item; item = c.nodes[--j];) {
|
||||
if (item.c) continue;
|
||||
if (item.name == 'img') {
|
||||
this.imgList.setItem(item.attrs.i, item.attrs.src);
|
||||
// #ifndef MP-ALIPAY
|
||||
if (!c.observer && !c.imgLoad && item.attrs.i != '0') {
|
||||
if (this.lazyLoad && uni.createIntersectionObserver) {
|
||||
c.observer = uni.createIntersectionObserver(c);
|
||||
c.observer.relativeToViewport({
|
||||
top: 900,
|
||||
bottom: 900
|
||||
}).observe('._img', () => {
|
||||
c.imgLoad = true;
|
||||
c.observer.disconnect();
|
||||
})
|
||||
} else
|
||||
c.imgLoad = true;
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
// #ifndef MP-ALIPAY
|
||||
else if (item.name == 'video') {
|
||||
var ctx = uni.createVideoContext(item.attrs.id, c);
|
||||
ctx.id = item.attrs.id;
|
||||
this.videoContexts.push(ctx);
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
|
||||
if (item.attrs && item.attrs.id) {
|
||||
this.anchors = this.anchors || [];
|
||||
this.anchors.push({
|
||||
id: item.attrs.id,
|
||||
node: c
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
if (c.$children.length)
|
||||
f(c.$children)
|
||||
}
|
||||
}
|
||||
f(this.$children);
|
||||
// #ifdef MP-TOUTIAO
|
||||
}, 200)
|
||||
this.$emit('load');
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
setTimeout(() => {
|
||||
this.loadVideo = true;
|
||||
}, 3000);
|
||||
// #endif
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
var height;
|
||||
clearInterval(this._timer);
|
||||
this._timer = setInterval(() => {
|
||||
// #ifdef H5
|
||||
var res = [this.rtf.getBoundingClientRect()];
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
// #ifdef APP-PLUS
|
||||
uni.createSelectorQuery().in(this)
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
this.createSelectorQuery()
|
||||
// #endif
|
||||
.select('#top').boundingClientRect().exec(res => {
|
||||
// #endif
|
||||
this.width = res[0].width;
|
||||
if (res[0].height == height) {
|
||||
this.$emit('ready', res[0])
|
||||
clearInterval(this._timer);
|
||||
}
|
||||
height = res[0].height;
|
||||
// #ifndef H5
|
||||
});
|
||||
// #endif
|
||||
}, 350)
|
||||
if (this.showWithAnimation && !append) this.showAm = 'animation:show .5s';
|
||||
// #endif
|
||||
},
|
||||
getText(ns = this.nodes) {
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
return this._text;
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
return this.rtf.innerText;
|
||||
// #endif
|
||||
// #ifndef H5 || APP-PLUS-NVUE
|
||||
var txt = '';
|
||||
for (var i = 0, n; n = ns[i++];) {
|
||||
if (n.type == 'text') txt += n.text.replace(/ /g, '\u00A0').replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/&/g, '&');
|
||||
else if (n.type == 'br') txt += '\n';
|
||||
else {
|
||||
// 块级标签前后加换行
|
||||
var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
|
||||
'0' && n.name[1] < '7');
|
||||
if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
|
||||
if (n.children) txt += this.getText(n.children);
|
||||
if (block && txt[txt.length - 1] != '\n') txt += '\n';
|
||||
else if (n.name == 'td' || n.name == 'th') txt += '\t';
|
||||
}
|
||||
}
|
||||
return txt;
|
||||
// #endif
|
||||
},
|
||||
navigateTo(obj) {
|
||||
if (!this.useAnchor)
|
||||
return obj.fail && obj.fail({
|
||||
errMsg: 'Anchor is disabled'
|
||||
})
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (!obj.id)
|
||||
dom.scrollToElement(this.$refs.web);
|
||||
else
|
||||
this.$refs.web.evalJs('var pos=document.getElementById("' + obj.id +
|
||||
'");if(pos)post({action:"linkpress",href:"#",offset:pos.offsetTop})');
|
||||
return obj.success && obj.success({
|
||||
errMsg: 'pageScrollTo:ok'
|
||||
});
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
if (!obj.id) {
|
||||
window.scrollTo(0, this.rtf.offsetTop);
|
||||
return obj.success && obj.success({
|
||||
errMsg: 'pageScrollTo:ok'
|
||||
});
|
||||
}
|
||||
var target = document.getElementById(obj.id);
|
||||
if (!target) return obj.fail && obj.fail({
|
||||
errMsg: 'Label not found'
|
||||
});
|
||||
obj.scrollTop = this.rtf.offsetTop + target.offsetTop;
|
||||
uni.pageScrollTo(obj);
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
var Scroll = (selector, component) => {
|
||||
uni.createSelectorQuery().in(component ? component : this).select(selector).boundingClientRect().selectViewport()
|
||||
.scrollOffset()
|
||||
.exec(res => {
|
||||
if (!res || !res[0])
|
||||
return obj.fail && obj.fail({
|
||||
errMsg: 'Label not found'
|
||||
});
|
||||
obj.scrollTop = res[1].scrollTop + res[0].top;
|
||||
uni.pageScrollTo(obj);
|
||||
})
|
||||
}
|
||||
if (!obj.id) Scroll('#top');
|
||||
else {
|
||||
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
|
||||
Scroll('#top >>> #' + obj.id + ', #top >>> .' + obj.id);
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
|
||||
for (var anchor of this.anchors)
|
||||
if (anchor.id == obj.id)
|
||||
Scroll('#' + obj.id + ', .' + obj.id, anchor.node);
|
||||
// #endif
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
getVideoContext(id) {
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
if (!id) return this.videoContexts;
|
||||
else
|
||||
for (var i = this.videoContexts.length; i--;)
|
||||
if (this.videoContexts[i].id == id) return this.videoContexts[i];
|
||||
// #endif
|
||||
},
|
||||
// 预加载
|
||||
preLoad(html, num) {
|
||||
// #ifdef H5 || APP-PLUS-NVUE
|
||||
if (html.constructor == Array)
|
||||
html = this._Dom2Str(html);
|
||||
var script = "var contain=document.createElement('div');contain.innerHTML='" + html.replace(/'/g, "\\'") +
|
||||
"';for(var imgs=contain.querySelectorAll('img'),i=imgs.length-1;i>=" + num +
|
||||
";i--)imgs[i].removeAttribute('src');";
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
this.$refs.web.evalJs(script);
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
eval(script);
|
||||
// #endif
|
||||
// #ifndef H5 || APP-PLUS-NVUE
|
||||
if (typeof html == 'string') {
|
||||
var id = hash(html);
|
||||
html = new Parser(html, this).parse();
|
||||
cache[id] = html;
|
||||
}
|
||||
var wait = [];
|
||||
(function f(ns) {
|
||||
for (var i = 0, n; n = ns[i++];) {
|
||||
if (n.name == 'img' && n.attrs.src && !wait.includes(n.attrs.src))
|
||||
wait.push(n.attrs.src);
|
||||
f(n.children || []);
|
||||
}
|
||||
})(html);
|
||||
if (num) wait = wait.slice(0, num);
|
||||
this._wait = (this._wait || []).concat(wait);
|
||||
if (!this.imgs) this.imgs = this._wait.splice(0, 15);
|
||||
else if (this.imgs.length < 15)
|
||||
this.imgs = this.imgs.concat(this._wait.splice(0, 15 - this.imgs.length));
|
||||
// #endif
|
||||
},
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
_message(e) {
|
||||
// 接收 web-view 消息
|
||||
var data = e.detail.data[0];
|
||||
if (data.action == 'load') {
|
||||
this.$emit('load');
|
||||
this._text = data.text;
|
||||
} else if (data.action == 'getTitle') {
|
||||
if (this.autosetTitle)
|
||||
uni.setNavigationBarTitle({
|
||||
title: data.title
|
||||
})
|
||||
} else if (data.action == 'getImgList') {
|
||||
this.imgList.length = 0;
|
||||
for (var i = data.imgList.length; i--;)
|
||||
this.imgList.setItem(i, data.imgList[i]);
|
||||
} else if (data.action == 'preview') {
|
||||
var preview = true;
|
||||
data.img.ignore = () => preview = false;
|
||||
this.$emit('imgtap', data.img);
|
||||
if (preview)
|
||||
uni.previewImage({
|
||||
current: data.img.i,
|
||||
urls: this.imgList
|
||||
})
|
||||
} else if (data.action == 'linkpress') {
|
||||
var jump = true,
|
||||
href = data.href;
|
||||
this.$emit('linkpress', {
|
||||
href,
|
||||
ignore: () => jump = false
|
||||
})
|
||||
if (jump && href) {
|
||||
if (href[0] == '#') {
|
||||
if (this.useAnchor)
|
||||
dom.scrollToElement(this.$refs.web, {
|
||||
offset: data.offset
|
||||
})
|
||||
} else if (href.includes('://'))
|
||||
plus.runtime.openWeb(href);
|
||||
else
|
||||
uni.navigateTo({
|
||||
url: href
|
||||
})
|
||||
}
|
||||
} else if (data.action == 'error')
|
||||
this.$emit('error', {
|
||||
source: data.source,
|
||||
target: data.target
|
||||
})
|
||||
else if (data.action == 'ready') {
|
||||
this.height = data.height;
|
||||
this.$nextTick(() => {
|
||||
uni.createSelectorQuery().in(this).select('#top').boundingClientRect().exec(res => {
|
||||
this.rect = res[0];
|
||||
this.$emit('ready', res[0]);
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
// #ifndef H5
|
||||
_load(e) {
|
||||
if (this._wait.length)
|
||||
this.$set(this.imgs, e.target.id, this._wait.shift());
|
||||
},
|
||||
// #endif
|
||||
_tap(e) {
|
||||
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
|
||||
if (this.gestureZoom && e.timeStamp - this._lastT < 300) {
|
||||
var initY = e.touches[0].pageY - e.currentTarget.offsetTop;
|
||||
if (this._zoom) {
|
||||
this._scaleAm.translateX(0).scale(1).step();
|
||||
uni.pageScrollTo({
|
||||
scrollTop: (initY + this._initY) / 2 - e.touches[0].clientY,
|
||||
duration: 400
|
||||
})
|
||||
} else {
|
||||
var initX = e.touches[0].pageX - e.currentTarget.offsetLeft;
|
||||
this._initY = initY;
|
||||
this._scaleAm = uni.createAnimation({
|
||||
transformOrigin: `${initX}px ${this._initY}px 0`,
|
||||
timingFunction: 'ease-in-out'
|
||||
});
|
||||
// #ifdef MP-TOUTIAO
|
||||
this._scaleAm.opacity(1);
|
||||
// #endif
|
||||
this._scaleAm.scale(2).step();
|
||||
this._tMax = initX / 2;
|
||||
this._tMin = (initX - this.width) / 2;
|
||||
this._tX = 0;
|
||||
}
|
||||
this._zoom = !this._zoom;
|
||||
this.scaleAm = this._scaleAm.export();
|
||||
}
|
||||
this._lastT = e.timeStamp;
|
||||
// #endif
|
||||
},
|
||||
_touchstart(e) {
|
||||
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
|
||||
if (e.touches.length == 1)
|
||||
this._initX = this._lastX = e.touches[0].pageX;
|
||||
// #endif
|
||||
},
|
||||
_touchmove(e) {
|
||||
// #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
|
||||
var diff = e.touches[0].pageX - this._lastX;
|
||||
if (this._zoom && e.touches.length == 1 && Math.abs(diff) > 20) {
|
||||
this._lastX = e.touches[0].pageX;
|
||||
if ((this._tX <= this._tMin && diff < 0) || (this._tX >= this._tMax && diff > 0))
|
||||
return;
|
||||
this._tX += (diff * Math.abs(this._lastX - this._initX) * 0.05);
|
||||
if (this._tX < this._tMin) this._tX = this._tMin;
|
||||
if (this._tX > this._tMax) this._tX = this._tMax;
|
||||
this._scaleAm.translateX(this._tX).step();
|
||||
this.scaleAm = this._scaleAm.export();
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@keyframes show {
|
||||
0% {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
:host {
|
||||
display: block;
|
||||
overflow: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
||||
102
components/jyf-parser/libs/CssHandler.js
Normal file
102
components/jyf-parser/libs/CssHandler.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
解析和匹配 Css 的选择器
|
||||
github:https://github.com/jin-yufeng/Parser
|
||||
docs:https://jin-yufeng.github.io/Parser
|
||||
author:JinYufeng
|
||||
update:2020/03/15
|
||||
*/
|
||||
var cfg = require('./config.js');
|
||||
class CssHandler {
|
||||
constructor(tagStyle) {
|
||||
var styles = Object.assign({}, cfg.userAgentStyles);
|
||||
for (var item in tagStyle)
|
||||
styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item];
|
||||
this.styles = styles;
|
||||
}
|
||||
getStyle = data => this.styles = new CssParser(data, this.styles).parse();
|
||||
match(name, attrs) {
|
||||
var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : '';
|
||||
if (attrs.class) {
|
||||
var items = attrs.class.split(' ');
|
||||
for (var i = 0, item; item = items[i]; i++)
|
||||
if (tmp = this.styles['.' + item])
|
||||
matched += tmp + ';';
|
||||
}
|
||||
if (tmp = this.styles['#' + attrs.id])
|
||||
matched += tmp + ';';
|
||||
return matched;
|
||||
}
|
||||
}
|
||||
module.exports = CssHandler;
|
||||
class CssParser {
|
||||
constructor(data, init) {
|
||||
this.data = data;
|
||||
this.floor = 0;
|
||||
this.i = 0;
|
||||
this.list = [];
|
||||
this.res = init;
|
||||
this.state = this.Space;
|
||||
}
|
||||
parse() {
|
||||
for (var c; c = this.data[this.i]; this.i++)
|
||||
this.state(c);
|
||||
return this.res;
|
||||
}
|
||||
section = () => this.data.substring(this.start, this.i);
|
||||
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
// 状态机
|
||||
Space(c) {
|
||||
if (c == '.' || c == '#' || this.isLetter(c)) {
|
||||
this.start = this.i;
|
||||
this.state = this.Name;
|
||||
} else if (c == '/' && this.data[this.i + 1] == '*')
|
||||
this.Comment();
|
||||
else if (!cfg.blankChar[c] && c != ';')
|
||||
this.state = this.Ignore;
|
||||
}
|
||||
Comment() {
|
||||
this.i = this.data.indexOf('*/', this.i) + 1;
|
||||
if (!this.i) this.i = this.data.length;
|
||||
this.state = this.Space;
|
||||
}
|
||||
Ignore(c) {
|
||||
if (c == '{') this.floor++;
|
||||
else if (c == '}' && !--this.floor) this.state = this.Space;
|
||||
}
|
||||
Name(c) {
|
||||
if (cfg.blankChar[c]) {
|
||||
this.list.push(this.section());
|
||||
this.state = this.NameSpace;
|
||||
} else if (c == '{') {
|
||||
this.list.push(this.section());
|
||||
this.Content();
|
||||
} else if (c == ',') {
|
||||
this.list.push(this.section());
|
||||
this.Comma();
|
||||
} else if (!this.isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_')
|
||||
this.state = this.Ignore;
|
||||
}
|
||||
NameSpace(c) {
|
||||
if (c == '{') this.Content();
|
||||
else if (c == ',') this.Comma();
|
||||
else if (!cfg.blankChar[c]) this.state = this.Ignore;
|
||||
}
|
||||
Comma() {
|
||||
while (cfg.blankChar[this.data[++this.i]]);
|
||||
if (this.data[this.i] == '{') this.Content();
|
||||
else {
|
||||
this.start = this.i--;
|
||||
this.state = this.Name;
|
||||
}
|
||||
}
|
||||
Content() {
|
||||
this.start = ++this.i;
|
||||
if ((this.i = this.data.indexOf('}', this.i)) == -1) this.i = this.data.length;
|
||||
var content = this.section();
|
||||
for (var i = 0, item; item = this.list[i++];)
|
||||
if (this.res[item]) this.res[item] += ';' + content;
|
||||
else this.res[item] = content;
|
||||
this.list = [];
|
||||
this.state = this.Space;
|
||||
}
|
||||
}
|
||||
577
components/jyf-parser/libs/MpHtmlParser.js
Normal file
577
components/jyf-parser/libs/MpHtmlParser.js
Normal file
@@ -0,0 +1,577 @@
|
||||
/*
|
||||
将 html 解析为适用于小程序 rich-text 的 DOM 结构
|
||||
github:https://github.com/jin-yufeng/Parser
|
||||
docs:https://jin-yufeng.github.io/Parser
|
||||
author:JinYufeng
|
||||
update:2020/04/13
|
||||
*/
|
||||
var cfg = require('./config.js'),
|
||||
blankChar = cfg.blankChar,
|
||||
CssHandler = require('./CssHandler.js'),
|
||||
{
|
||||
screenWidth,
|
||||
system
|
||||
} = wx.getSystemInfoSync();
|
||||
// #ifdef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO
|
||||
var entities = {
|
||||
lt: '<',
|
||||
gt: '>',
|
||||
amp: '&',
|
||||
quot: '"',
|
||||
apos: "'",
|
||||
nbsp: '\xA0',
|
||||
ensp: '\u2002',
|
||||
emsp: '\u2003',
|
||||
ndash: '–',
|
||||
mdash: '—',
|
||||
middot: '·',
|
||||
lsquo: '‘',
|
||||
rsquo: '’',
|
||||
ldquo: '“',
|
||||
rdquo: '”',
|
||||
bull: '•',
|
||||
hellip: '…',
|
||||
permil: '‰',
|
||||
copy: '©',
|
||||
reg: '®',
|
||||
trade: '™',
|
||||
times: '×',
|
||||
divide: '÷',
|
||||
cent: '¢',
|
||||
pound: '£',
|
||||
yen: '¥',
|
||||
euro: '€',
|
||||
sect: '§'
|
||||
};
|
||||
// #endif
|
||||
var emoji; // emoji 补丁包 https://jin-yufeng.github.io/Parser/#/instructions?id=emoji
|
||||
class MpHtmlParser {
|
||||
constructor(data, options = {}) {
|
||||
this.attrs = {};
|
||||
this.compress = options.compress;
|
||||
this.CssHandler = new CssHandler(options.tagStyle, screenWidth);
|
||||
this.data = data;
|
||||
this.domain = options.domain;
|
||||
this.DOM = [];
|
||||
this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0;
|
||||
this.protocol = this.domain && this.domain.includes('://') ? this.domain.split('://')[0] : '';
|
||||
this.state = this.Text;
|
||||
this.STACK = [];
|
||||
this.useAnchor = options.useAnchor;
|
||||
this.xml = options.xml;
|
||||
}
|
||||
parse() {
|
||||
if (emoji) this.data = emoji.parseEmoji(this.data);
|
||||
for (var c; c = this.data[this.i]; this.i++)
|
||||
this.state(c);
|
||||
if (this.state == this.Text) this.setText();
|
||||
while (this.STACK.length) this.popNode(this.STACK.pop());
|
||||
// #ifdef MP-BAIDU || MP-TOUTIAO
|
||||
// 将顶层标签的一些样式提取出来给 rich-text
|
||||
(function f(ns) {
|
||||
for (var i = ns.length, n; n = ns[--i];) {
|
||||
if (n.type == 'text') continue;
|
||||
if (!n.c) {
|
||||
var style = n.attrs.style;
|
||||
if (style) {
|
||||
var j, k, res;
|
||||
if ((j = style.indexOf('display')) != -1)
|
||||
res = style.substring(j, (k = style.indexOf(';', j)) == -1 ? style.length : k);
|
||||
if ((j = style.indexOf('float')) != -1)
|
||||
res += ';' + style.substring(j, (k = style.indexOf(';', j)) == -1 ? style.length : k);
|
||||
n.attrs.contain = res;
|
||||
}
|
||||
} else f(n.children);
|
||||
}
|
||||
})(this.DOM);
|
||||
// #endif
|
||||
if (this.DOM.length) {
|
||||
this.DOM[0].PoweredBy = 'Parser';
|
||||
if (this.title) this.DOM[0].title = this.title;
|
||||
}
|
||||
return this.DOM;
|
||||
}
|
||||
// 设置属性
|
||||
setAttr() {
|
||||
var name = this.getName(this.attrName);
|
||||
if (cfg.trustAttrs[name]) {
|
||||
if (!this.attrVal) {
|
||||
if (cfg.boolAttrs[name]) this.attrs[name] = 'T';
|
||||
} else if (name == 'src') this.attrs[name] = this.getUrl(this.attrVal.replace(/&/g, '&'));
|
||||
else this.attrs[name] = this.attrVal;
|
||||
}
|
||||
this.attrVal = '';
|
||||
while (blankChar[this.data[this.i]]) this.i++;
|
||||
if (this.isClose()) this.setNode();
|
||||
else {
|
||||
this.start = this.i;
|
||||
this.state = this.AttrName;
|
||||
}
|
||||
}
|
||||
// 设置文本节点
|
||||
setText() {
|
||||
var back, text = this.section();
|
||||
if (!text) return;
|
||||
text = (cfg.onText && cfg.onText(text, () => back = true)) || text;
|
||||
if (back) {
|
||||
this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i);
|
||||
let j = this.start + text.length;
|
||||
for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]);
|
||||
return;
|
||||
}
|
||||
if (!this.pre) {
|
||||
// 合并空白符
|
||||
var tmp = [];
|
||||
for (let i = text.length, c; c = text[--i];)
|
||||
if (!blankChar[c] || (!blankChar[tmp[0]] && (c = ' '))) tmp.unshift(c);
|
||||
text = tmp.join('');
|
||||
if (text == ' ') return;
|
||||
}
|
||||
// 处理实体
|
||||
var siblings = this.siblings(),
|
||||
i = -1,
|
||||
j, en;
|
||||
while (1) {
|
||||
if ((i = text.indexOf('&', i + 1)) == -1) break;
|
||||
if ((j = text.indexOf(';', i + 2)) == -1) break;
|
||||
if (text[i + 1] == '#') {
|
||||
en = parseInt((text[i + 2] == 'x' ? '0' : '') + text.substring(i + 2, j));
|
||||
if (!isNaN(en)) text = text.substr(0, i) + String.fromCharCode(en) + text.substring(j + 1);
|
||||
} else {
|
||||
en = text.substring(i + 1, j);
|
||||
// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
|
||||
if (en == 'nbsp') text = text.substr(0, i) + '\xA0' + text.substr(j + 1); // 解决 失效
|
||||
else if (en != 'lt' && en != 'gt' && en != 'amp' && en != 'ensp' && en != 'emsp' && en != 'quot' && en != 'apos') {
|
||||
i && siblings.push({
|
||||
type: 'text',
|
||||
text: text.substr(0, i)
|
||||
})
|
||||
siblings.push({
|
||||
type: 'text',
|
||||
text: `&${en};`,
|
||||
en: 1
|
||||
})
|
||||
text = text.substr(j + 1);
|
||||
i = -1;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO
|
||||
if (entities[en]) text = text.substr(0, i) + entities[en] + text.substr(j + 1);
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
text && siblings.push({
|
||||
type: 'text',
|
||||
text
|
||||
})
|
||||
}
|
||||
// 设置元素节点
|
||||
setNode() {
|
||||
var node = {
|
||||
name: this.tagName.toLowerCase(),
|
||||
attrs: this.attrs
|
||||
},
|
||||
close = cfg.selfClosingTags[node.name] || (this.xml && this.data[this.i] == '/');
|
||||
this.attrs = {};
|
||||
if (!cfg.ignoreTags[node.name]) {
|
||||
this.matchAttr(node);
|
||||
if (!close) {
|
||||
node.children = [];
|
||||
if (node.name == 'pre' && cfg.highlight) {
|
||||
this.remove(node);
|
||||
this.pre = node.pre = true;
|
||||
}
|
||||
this.siblings().push(node);
|
||||
this.STACK.push(node);
|
||||
} else if (!cfg.filter || cfg.filter(node, this) != false)
|
||||
this.siblings().push(node);
|
||||
} else {
|
||||
if (!close) this.remove(node);
|
||||
else if (node.name == 'source') {
|
||||
var parent = this.STACK[this.STACK.length - 1],
|
||||
attrs = node.attrs;
|
||||
if (parent && attrs.src)
|
||||
if (parent.name == 'video' || parent.name == 'audio')
|
||||
parent.attrs.source.push(attrs.src);
|
||||
else {
|
||||
var i, media = attrs.media;
|
||||
if (parent.name == 'picture' && !parent.attrs.src && !(attrs.src.indexOf('.webp') && system.includes('iOS')) &&
|
||||
(!media || (media.includes('px') &&
|
||||
(((i = media.indexOf('min-width')) != -1 && (i = media.indexOf(':', i + 8)) != -1 && screenWidth > parseInt(
|
||||
media.substr(i + 1))) ||
|
||||
((i = media.indexOf('max-width')) != -1 && (i = media.indexOf(':', i + 8)) != -1 && screenWidth < parseInt(
|
||||
media.substr(i + 1)))))))
|
||||
parent.attrs.src = attrs.src;
|
||||
}
|
||||
} else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href;
|
||||
}
|
||||
if (this.data[this.i] == '/') this.i++;
|
||||
this.start = this.i + 1;
|
||||
this.state = this.Text;
|
||||
}
|
||||
// 移除标签
|
||||
remove(node) {
|
||||
var name = node.name,
|
||||
j = this.i;
|
||||
while (1) {
|
||||
if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) {
|
||||
if (name == 'pre' || name == 'svg') this.i = j;
|
||||
else this.i = this.data.length;
|
||||
return;
|
||||
}
|
||||
this.start = (this.i += 2);
|
||||
while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++;
|
||||
if (this.getName(this.section()) == name) {
|
||||
// 代码块高亮
|
||||
if (name == 'pre') {
|
||||
this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) +
|
||||
this.data.substr(this.i - 5);
|
||||
return this.i = j;
|
||||
} else if (name == 'style')
|
||||
this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7));
|
||||
else if (name == 'title')
|
||||
this.title = this.data.substring(j + 1, this.i - 7);
|
||||
if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length;
|
||||
// 处理 svg
|
||||
if (name == 'svg') {
|
||||
var src = this.data.substring(j, this.i + 1);
|
||||
if (!node.attrs.xmlns) src = ' xmlns="http://www.w3.org/2000/svg"' + src;
|
||||
var i = j;
|
||||
while (this.data[j] != '<') j--;
|
||||
src = this.data.substring(j, i) + src;
|
||||
var parent = this.STACK[this.STACK.length - 1];
|
||||
if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline'))
|
||||
parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style;
|
||||
this.siblings().push({
|
||||
name: 'img',
|
||||
attrs: {
|
||||
src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
|
||||
ignore: 'T'
|
||||
}
|
||||
})
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理属性
|
||||
matchAttr(node) {
|
||||
var attrs = node.attrs,
|
||||
style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''),
|
||||
styleObj = {};
|
||||
if (attrs.id) {
|
||||
if (this.compress & 1) attrs.id = void 0;
|
||||
else if (this.useAnchor) this.bubble();
|
||||
}
|
||||
if ((this.compress & 2) && attrs.class) attrs.class = void 0;
|
||||
switch (node.name) {
|
||||
case 'img':
|
||||
if (attrs['data-src']) {
|
||||
attrs.src = attrs.src || attrs['data-src'];
|
||||
attrs['data-src'] = void 0;
|
||||
}
|
||||
if (attrs.src && !attrs.ignore) {
|
||||
if (this.bubble()) attrs.i = (this.imgNum++).toString();
|
||||
else attrs.ignore = 'T';
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
case 'ad':
|
||||
// #ifdef APP-PLUS
|
||||
case 'iframe':
|
||||
case 'embed':
|
||||
// #endif
|
||||
this.bubble();
|
||||
break;
|
||||
case 'font':
|
||||
if (attrs.color) {
|
||||
styleObj['color'] = attrs.color;
|
||||
attrs.color = void 0;
|
||||
}
|
||||
if (attrs.face) {
|
||||
styleObj['font-family'] = attrs.face;
|
||||
attrs.face = void 0;
|
||||
}
|
||||
if (attrs.size) {
|
||||
var size = parseInt(attrs.size);
|
||||
if (size < 1) size = 1;
|
||||
else if (size > 7) size = 7;
|
||||
var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];
|
||||
styleObj['font-size'] = map[size - 1];
|
||||
attrs.size = void 0;
|
||||
}
|
||||
break;
|
||||
case 'video':
|
||||
case 'audio':
|
||||
if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]);
|
||||
else this[`${node.name}Num`]++;
|
||||
if (node.name == 'video') {
|
||||
if (attrs.width) {
|
||||
style = `width:${parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')};${style}`;
|
||||
attrs.width = void 0;
|
||||
}
|
||||
if (attrs.height) {
|
||||
style = `height:${parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')};${style}`;
|
||||
attrs.height = void 0;
|
||||
}
|
||||
if (this.videoNum > 3) node.lazyLoad = true;
|
||||
}
|
||||
attrs.source = [];
|
||||
if (attrs.src) attrs.source.push(attrs.src);
|
||||
if (!attrs.controls && !attrs.autoplay)
|
||||
console.warn(`存在没有 controls 属性的 ${node.name} 标签,可能导致无法播放`, node);
|
||||
this.bubble();
|
||||
break;
|
||||
case 'td':
|
||||
case 'th':
|
||||
if (attrs.colspan || attrs.rowspan)
|
||||
for (var k = this.STACK.length, item; item = this.STACK[--k];)
|
||||
if (item.name == 'table') {
|
||||
item.c = void 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (attrs.align) {
|
||||
styleObj['text-align'] = attrs.align;
|
||||
attrs.align = void 0;
|
||||
}
|
||||
// 压缩 style
|
||||
var styles = style.replace(/"/g, '"').replace(/&/g, '&').split(';');
|
||||
style = '';
|
||||
for (var i = 0, len = styles.length; i < len; i++) {
|
||||
var info = styles[i].split(':');
|
||||
if (info.length < 2) continue;
|
||||
let key = info[0].trim().toLowerCase(),
|
||||
value = info.slice(1).join(':').trim();
|
||||
if (value.includes('-webkit') || value.includes('-moz') || value.includes('-ms') || value.includes('-o') || value
|
||||
.includes(
|
||||
'safe'))
|
||||
style += `;${key}:${value}`;
|
||||
else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import'))
|
||||
styleObj[key] = value;
|
||||
}
|
||||
if (node.name == 'img' && parseInt(styleObj.width || attrs.width) > screenWidth)
|
||||
styleObj.height = 'auto';
|
||||
for (var key in styleObj) {
|
||||
var value = styleObj[key];
|
||||
if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1;
|
||||
// 填充链接
|
||||
if (value.includes('url')) {
|
||||
var j = value.indexOf('(');
|
||||
if (j++ != -1) {
|
||||
while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++;
|
||||
value = value.substr(0, j) + this.getUrl(value.substr(j));
|
||||
}
|
||||
}
|
||||
// 转换 rpx
|
||||
else if (value.includes('rpx'))
|
||||
value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * screenWidth / 750 + 'px');
|
||||
else if (key == 'white-space' && value.includes('pre'))
|
||||
this.pre = node.pre = true;
|
||||
style += `;${key}:${value}`;
|
||||
}
|
||||
style = style.substr(1);
|
||||
if (style) attrs.style = style;
|
||||
}
|
||||
// 节点出栈处理
|
||||
popNode(node) {
|
||||
// 空白符处理
|
||||
if (node.pre) {
|
||||
node.pre = this.pre = void 0;
|
||||
for (let i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].pre)
|
||||
this.pre = true;
|
||||
}
|
||||
if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false))
|
||||
return this.siblings().pop();
|
||||
var attrs = node.attrs;
|
||||
// 替换一些标签名
|
||||
if (node.name == 'picture') {
|
||||
node.name = 'img';
|
||||
if (!attrs.src && (node.children[0] || '').name == 'img')
|
||||
attrs.src = node.children[0].attrs.src;
|
||||
if (attrs.src && !attrs.ignore)
|
||||
attrs.i = (this.imgNum++).toString();
|
||||
return node.children = void 0;
|
||||
}
|
||||
if (cfg.blockTags[node.name]) node.name = 'div';
|
||||
else if (!cfg.trustTags[node.name]) node.name = 'span';
|
||||
// 处理列表
|
||||
if (node.c) {
|
||||
if (node.name == 'ul') {
|
||||
var floor = 1;
|
||||
for (let i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].name == 'ul') floor++;
|
||||
if (floor != 1)
|
||||
for (let i = node.children.length; i--;)
|
||||
node.children[i].floor = floor;
|
||||
} else if (node.name == 'ol') {
|
||||
for (let i = 0, num = 1, child; child = node.children[i++];)
|
||||
if (child.name == 'li') {
|
||||
child.type = 'ol';
|
||||
child.num = ((num, type) => {
|
||||
if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26);
|
||||
if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26);
|
||||
if (type == 'i' || type == 'I') {
|
||||
num = (num - 1) % 99 + 1;
|
||||
var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
|
||||
ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],
|
||||
res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || '');
|
||||
if (type == 'i') return res.toLowerCase();
|
||||
return res;
|
||||
}
|
||||
return num;
|
||||
})(num++, attrs.type) + '.';
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理表格的边框
|
||||
if (node.name == 'table') {
|
||||
var padding = attrs.cellpadding,
|
||||
spacing = attrs.cellspacing,
|
||||
border = attrs.border;
|
||||
if (node.c) {
|
||||
this.bubble();
|
||||
if (!padding) padding = 2;
|
||||
if (!spacing) spacing = 2;
|
||||
}
|
||||
if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;
|
||||
if (spacing) attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`;
|
||||
if (border || padding)
|
||||
(function f(ns) {
|
||||
for (var i = 0, n; n = ns[i]; i++) {
|
||||
if (n.name == 'th' || n.name == 'td') {
|
||||
if (border) n.attrs.style = `border:${border}px solid gray;${n.attrs.style}`;
|
||||
if (padding) n.attrs.style = `padding:${padding}px;${n.attrs.style}`;
|
||||
} else f(n.children || []);
|
||||
}
|
||||
})(node.children)
|
||||
}
|
||||
this.CssHandler.pop && this.CssHandler.pop(node);
|
||||
// 自动压缩
|
||||
if (node.name == 'div' && !Object.keys(attrs).length) {
|
||||
var siblings = this.siblings();
|
||||
if (node.children.length == 1 && node.children[0].name == 'div')
|
||||
siblings[siblings.length - 1] = node.children[0];
|
||||
}
|
||||
}
|
||||
// 工具函数
|
||||
bubble() {
|
||||
for (var i = this.STACK.length, item; item = this.STACK[--i];) {
|
||||
if (cfg.richOnlyTags[item.name]) {
|
||||
if (item.name == 'table' && !Object.hasOwnProperty.call(item, 'c')) item.c = 1;
|
||||
return false;
|
||||
}
|
||||
item.c = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
getName = val => this.xml ? val : val.toLowerCase();
|
||||
getUrl(url) {
|
||||
if (url[0] == '/') {
|
||||
if (url[1] == '/') url = this.protocol + ':' + url;
|
||||
else if (this.domain) url = this.domain + url;
|
||||
} else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://'))
|
||||
url = this.domain + '/' + url;
|
||||
return url;
|
||||
}
|
||||
isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');
|
||||
section = () => this.data.substring(this.start, this.i);
|
||||
siblings = () => this.STACK.length ? this.STACK[this.STACK.length - 1].children : this.DOM;
|
||||
// 状态机
|
||||
Text(c) {
|
||||
if (c == '<') {
|
||||
var next = this.data[this.i + 1],
|
||||
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
if (isLetter(next)) {
|
||||
this.setText();
|
||||
this.start = this.i + 1;
|
||||
this.state = this.TagName;
|
||||
} else if (next == '/') {
|
||||
this.setText();
|
||||
if (isLetter(this.data[++this.i + 1])) {
|
||||
this.start = this.i + 1;
|
||||
this.state = this.EndTag;
|
||||
} else
|
||||
this.Comment();
|
||||
} else if (next == '!') {
|
||||
this.setText();
|
||||
this.Comment();
|
||||
}
|
||||
}
|
||||
}
|
||||
Comment() {
|
||||
var key;
|
||||
if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->';
|
||||
else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>';
|
||||
else key = '>';
|
||||
if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length;
|
||||
else this.i += key.length - 1;
|
||||
this.start = this.i + 1;
|
||||
this.state = this.Text;
|
||||
}
|
||||
TagName(c) {
|
||||
if (blankChar[c]) {
|
||||
this.tagName = this.section();
|
||||
while (blankChar[this.data[this.i]]) this.i++;
|
||||
if (this.isClose()) this.setNode();
|
||||
else {
|
||||
this.start = this.i;
|
||||
this.state = this.AttrName;
|
||||
}
|
||||
} else if (this.isClose()) {
|
||||
this.tagName = this.section();
|
||||
this.setNode();
|
||||
}
|
||||
}
|
||||
AttrName(c) {
|
||||
var blank = blankChar[c];
|
||||
if (blank) {
|
||||
this.attrName = this.section();
|
||||
c = this.data[this.i];
|
||||
}
|
||||
if (c == '=') {
|
||||
if (!blank) this.attrName = this.section();
|
||||
while (blankChar[this.data[++this.i]]);
|
||||
this.start = this.i--;
|
||||
this.state = this.AttrValue;
|
||||
} else if (blank) this.setAttr();
|
||||
else if (this.isClose()) {
|
||||
this.attrName = this.section();
|
||||
this.setAttr();
|
||||
}
|
||||
}
|
||||
AttrValue(c) {
|
||||
if (c == '"' || c == "'") {
|
||||
this.start++;
|
||||
if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length;
|
||||
this.attrVal = this.section();
|
||||
this.i++;
|
||||
} else {
|
||||
for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++);
|
||||
this.attrVal = this.section();
|
||||
}
|
||||
this.setAttr();
|
||||
}
|
||||
EndTag(c) {
|
||||
if (blankChar[c] || c == '>' || c == '/') {
|
||||
var name = this.getName(this.section());
|
||||
for (var i = this.STACK.length; i--;)
|
||||
if (this.STACK[i].name == name) break;
|
||||
if (i != -1) {
|
||||
var node;
|
||||
while ((node = this.STACK.pop()).name != name);
|
||||
this.popNode(node);
|
||||
} else if (name == 'p' || name == 'br')
|
||||
this.siblings().push({
|
||||
name,
|
||||
attrs: {}
|
||||
});
|
||||
this.i = this.data.indexOf('>', this.i);
|
||||
this.start = this.i + 1;
|
||||
if (this.i == -1) this.i = this.data.length;
|
||||
else this.state = this.Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = MpHtmlParser;
|
||||
80
components/jyf-parser/libs/config.js
Normal file
80
components/jyf-parser/libs/config.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/* 配置文件 */
|
||||
// #ifdef MP-WEIXIN
|
||||
const canIUse = wx.canIUse('editor'); // 高基础库标识,用于兼容
|
||||
// #endif
|
||||
module.exports = {
|
||||
// 过滤器函数
|
||||
filter: null,
|
||||
// 代码高亮函数
|
||||
highlight: null,
|
||||
// 文本处理函数
|
||||
onText: null,
|
||||
blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'),
|
||||
// 块级标签,将被转为 div
|
||||
blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,section' + (
|
||||
// #ifdef MP-WEIXIN
|
||||
canIUse ? '' :
|
||||
// #endif
|
||||
',pre')),
|
||||
// 将被移除的标签
|
||||
ignoreTags: makeMap(
|
||||
'area,base,basefont,canvas,command,frame,input,isindex,keygen,link,map,meta,param,script,source,style,svg,textarea,title,track,use,wbr'
|
||||
// #ifdef MP-WEIXIN
|
||||
+ (canIUse ? ',rp' : '')
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
+ ',embed,iframe'
|
||||
// #endif
|
||||
),
|
||||
// 只能被 rich-text 显示的标签
|
||||
richOnlyTags: makeMap('a,colgroup,fieldset,legend,picture,table'
|
||||
// #ifdef MP-WEIXIN
|
||||
+ (canIUse ? ',bdi,bdo,caption,rt,ruby' : '')
|
||||
// #endif
|
||||
),
|
||||
// 自闭合的标签
|
||||
selfClosingTags: makeMap(
|
||||
'area,base,basefont,br,col,circle,ellipse,embed,frame,hr,img,input,isindex,keygen,line,link,meta,param,path,polygon,rect,source,track,use,wbr'
|
||||
),
|
||||
// 信任的属性
|
||||
trustAttrs: makeMap(
|
||||
'align,alt,app-id,author,autoplay,border,cellpadding,cellspacing,class,color,colspan,controls,data-src,dir,face,height,href,id,ignore,loop,media,muted,name,path,poster,rowspan,size,span,src,start,style,type,unit-id,width,xmlns'
|
||||
),
|
||||
// bool 型的属性
|
||||
boolAttrs: makeMap('autoplay,controls,ignore,loop,muted'),
|
||||
// 信任的标签
|
||||
trustTags: makeMap(
|
||||
'a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'
|
||||
// #ifdef MP-WEIXIN
|
||||
+ (canIUse ? ',bdi,bdo,caption,pre,rt,ruby' : '')
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
+ ',embed,iframe'
|
||||
// #endif
|
||||
),
|
||||
// 默认的标签样式
|
||||
userAgentStyles: {
|
||||
address: 'font-style:italic',
|
||||
big: 'display:inline;font-size:1.2em',
|
||||
blockquote: 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px',
|
||||
caption: 'display:table-caption;text-align:center',
|
||||
center: 'text-align:center',
|
||||
cite: 'font-style:italic',
|
||||
dd: 'margin-left:40px',
|
||||
img: 'max-width:100%',
|
||||
mark: 'background-color:yellow',
|
||||
picture: 'max-width:100%',
|
||||
pre: 'font-family:monospace;white-space:pre;overflow:scroll',
|
||||
s: 'text-decoration:line-through',
|
||||
small: 'display:inline;font-size:0.8em',
|
||||
u: 'text-decoration:underline'
|
||||
}
|
||||
}
|
||||
|
||||
function makeMap(maprichee55text9oppplugin) {
|
||||
var map = {},
|
||||
list = maprichee55text9oppplugin.split(',');
|
||||
for (var i = list.length; i--;)
|
||||
map[list[i]] = true;
|
||||
return map;
|
||||
}
|
||||
35
components/jyf-parser/libs/handler.sjs
Normal file
35
components/jyf-parser/libs/handler.sjs
Normal file
@@ -0,0 +1,35 @@
|
||||
var inlineTags = {
|
||||
abbr: 1,
|
||||
b: 1,
|
||||
big: 1,
|
||||
code: 1,
|
||||
del: 1,
|
||||
em: 1,
|
||||
i: 1,
|
||||
ins: 1,
|
||||
label: 1,
|
||||
q: 1,
|
||||
small: 1,
|
||||
span: 1,
|
||||
strong: 1
|
||||
}
|
||||
export default {
|
||||
// 从顶层标签的样式中取出一些给 rich-text
|
||||
getStyle: function(style) {
|
||||
if (style) {
|
||||
var i, j, res = '';
|
||||
if ((i = style.indexOf('display')) != -1)
|
||||
res = style.substring(i, (j = style.indexOf(';', i)) == -1 ? style.length : j);
|
||||
if ((i = style.indexOf('float')) != -1)
|
||||
res += ';' + style.substring(i, (j = style.indexOf(';', i)) == -1 ? style.length : j);
|
||||
return res;
|
||||
}
|
||||
},
|
||||
getNode: function(item) {
|
||||
return [item];
|
||||
},
|
||||
// 是否通过 rich-text 显示
|
||||
useRichText: function(item) {
|
||||
return !item.c && !inlineTags[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1;
|
||||
}
|
||||
}
|
||||
44
components/jyf-parser/libs/handler.wxs
Normal file
44
components/jyf-parser/libs/handler.wxs
Normal file
@@ -0,0 +1,44 @@
|
||||
var inlineTags = {
|
||||
abbr: 1,
|
||||
b: 1,
|
||||
big: 1,
|
||||
code: 1,
|
||||
del: 1,
|
||||
em: 1,
|
||||
i: 1,
|
||||
ins: 1,
|
||||
label: 1,
|
||||
q: 1,
|
||||
small: 1,
|
||||
span: 1,
|
||||
strong: 1
|
||||
}
|
||||
module.exports = {
|
||||
// 从顶层标签的样式中取出一些给 rich-text
|
||||
getStyle: function(style) {
|
||||
if (style) {
|
||||
var i, j, res = '';
|
||||
if ((i = style.indexOf('display')) != -1)
|
||||
res = style.substring(i, (j = style.indexOf(';', i)) == -1 ? style.length : j);
|
||||
if ((i = style.indexOf('float')) != -1)
|
||||
res += ';' + style.substring(i, (j = style.indexOf(';', i)) == -1 ? style.length : j);
|
||||
return res;
|
||||
}
|
||||
},
|
||||
// 处理懒加载
|
||||
getNode: function(item, imgLoad) {
|
||||
if (!imgLoad && item.attrs.i != '0') {
|
||||
var img = {
|
||||
name: 'img',
|
||||
attrs: JSON.parse(JSON.stringify(item.attrs))
|
||||
}
|
||||
delete img.attrs.src;
|
||||
img.attrs.style += ';width:20px;height:20px';
|
||||
return [img];
|
||||
} else return [item];
|
||||
},
|
||||
// 是否通过 rich-text 显示
|
||||
useRichText: function(item) {
|
||||
return !item.c && !inlineTags[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1;
|
||||
}
|
||||
}
|
||||
476
components/jyf-parser/libs/trees.vue
Normal file
476
components/jyf-parser/libs/trees.vue
Normal file
@@ -0,0 +1,476 @@
|
||||
<!--
|
||||
trees 递归显示组件
|
||||
github:https://github.com/jin-yufeng/Parser
|
||||
docs:https://jin-yufeng.github.io/Parser
|
||||
插件市场:https://ext.dcloud.net.cn/plugin?id=805
|
||||
author:JinYufeng
|
||||
update:2020/04/13
|
||||
-->
|
||||
<template>
|
||||
<view class="interlayer">
|
||||
<block v-for="(n, index) in nodes" v-bind:key="index">
|
||||
<!--图片-->
|
||||
<!--#ifdef MP-WEIXIN || MP-QQ || MP-ALIPAY || APP-PLUS-->
|
||||
<rich-text v-if="n.name=='img'" :id="n.attrs.id" class="_img" :style="''+handler.getStyle(n.attrs.style)" :nodes="handler.getNode(n,!lazyLoad||imgLoad)"
|
||||
:data-attrs="n.attrs" @tap="imgtap" @longpress="imglongtap" />
|
||||
<!--#endif-->
|
||||
<!--#ifdef MP-BAIDU || MP-TOUTIAO-->
|
||||
<rich-text v-if="n.name=='img'" :id="n.attrs.id" class="_img" :style="n.attrs.contain" :nodes='[n]' :data-attrs="n.attrs"
|
||||
@tap="imgtap" @longpress="imglongtap" />
|
||||
<!--#endif-->
|
||||
<!--文本-->
|
||||
<!--#ifdef MP-WEIXIN || MP-QQ || APP-PLUS-->
|
||||
<rich-text v-else-if="n.decode" class="_entity" :nodes="[n]"></rich-text>
|
||||
<!--#endif-->
|
||||
<text v-else-if="n.type=='text'" decode>{{n.text}}</text>
|
||||
<text v-else-if="n.name=='br'">\n</text>
|
||||
<!--视频-->
|
||||
<view v-else-if="n.name=='video'">
|
||||
<view v-if="(!loadVideo||n.lazyLoad)&&!(controls[n.attrs.id]&&controls[n.attrs.id].play)" :id="n.attrs.id" :class="'_video '+(n.attrs.class||'')"
|
||||
:style="n.attrs.style" @tap="_loadVideo" />
|
||||
<video v-else :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||(controls[n.attrs.id]&&controls[n.attrs.id].play)"
|
||||
:controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[(controls[n.attrs.id]&&controls[n.attrs.id].index)||0]"
|
||||
:unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" data-from="video" data-source="source" @error="error" @play="play" />
|
||||
</view>
|
||||
<!--音频-->
|
||||
<audio v-else-if="n.name=='audio'" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :autoplay="n.attrs.autoplay"
|
||||
:controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.attrs.source[(controls[n.attrs.id]&&controls[n.attrs.id].index)||0]"
|
||||
:data-id="n.attrs.id" data-from="audio" data-source="source" @error="error" @play="play" />
|
||||
<!--链接-->
|
||||
<view v-else-if="n.name=='a'" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style"
|
||||
:data-attrs="n.attrs" @tap="linkpress">
|
||||
<trees class="_span" :nodes="n.children" />
|
||||
</view>
|
||||
<!--广告(按需打开注释)-->
|
||||
<!--#ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO-->
|
||||
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']"
|
||||
data-from="ad" @error="error" />-->
|
||||
<!--#endif-->
|
||||
<!--#ifdef MP-BAIDU-->
|
||||
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :appid="n.attrs.appid"
|
||||
:apid="n.attrs.apid" :type="n.attrs.type" data-from="ad" @error="error" />-->
|
||||
<!--#endif-->
|
||||
<!--#ifdef APP-PLUS-->
|
||||
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :adpid="n.attrs.adpid"
|
||||
data-from="ad" @error="error" />-->
|
||||
<!--#endif-->
|
||||
<!--列表-->
|
||||
<view v-else-if="n.name=='li'" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:flex'">
|
||||
<view v-if="n.type=='ol'" class="_ol-bef">{{n.num}}</view>
|
||||
<view v-else class="_ul-bef">
|
||||
<view v-if="n.floor%3==0" class="_ul-p1">█</view>
|
||||
<view v-else-if="n.floor%3==2" class="_ul-p2" />
|
||||
<view v-else class="_ul-p1" style="border-radius:50%">█</view>
|
||||
</view>
|
||||
<!--#ifdef MP-ALIPAY-->
|
||||
<view class="_li">
|
||||
<trees :nodes="n.children" />
|
||||
</view>
|
||||
<!--#endif-->
|
||||
<!--#ifndef MP-ALIPAY-->
|
||||
<trees class="_li" :nodes="n.children" :lazyLoad="lazyLoad" :loadVideo="loadVideo" />
|
||||
<!--#endif-->
|
||||
</view>
|
||||
<!--表格-->
|
||||
<view v-else-if="n.name=='table'&&n.c" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:table'">
|
||||
<view v-for="(tbody, i) in n.children" v-bind:key="i" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')">
|
||||
<view v-for="(tr, j) in tbody.children" v-bind:key="j" :class="tr.attrs.class" :style="(tr.attrs.style||'')+(tr.name[0]=='t'?';display:table-'+(tr.name=='tr'?'row':'cell'):'')">
|
||||
<trees v-if="tr.name=='td'" :nodes="tr.children" :lazyLoad="lazyLoad" :loadVideo="loadVideo" />
|
||||
<block v-else>
|
||||
<!--#ifdef MP-ALIPAY-->
|
||||
<view v-for="(td, k) in tr.children" v-bind:key="k" :class="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')">
|
||||
<trees :nodes="td.children" />
|
||||
</view>
|
||||
<!--#endif-->
|
||||
<!--#ifndef MP-ALIPAY-->
|
||||
<trees v-for="(td, k) in tr.children" v-bind:key="k" :class="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')"
|
||||
:nodes="td.children" :lazyLoad="lazyLoad" :loadVideo="loadVideo" />
|
||||
<!--#endif-->
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!--#ifdef APP-PLUS-->
|
||||
<iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder"
|
||||
:width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
|
||||
<embed v-else-if="n.name=='embed'" :style="n.attrs.style" :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
|
||||
<!--#endif-->
|
||||
<!--富文本-->
|
||||
<!--#ifdef MP-WEIXIN || MP-QQ || MP-ALIPAY || APP-PLUS-->
|
||||
<rich-text v-else-if="handler.useRichText(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" />
|
||||
<!--#endif-->
|
||||
<!--#ifdef MP-BAIDU || MP-TOUTIAO-->
|
||||
<rich-text v-else-if="!(n.c||n.continue)" :id="n.attrs.id" :class="_p" :style="n.attrs.contain" :nodes="[n]" />
|
||||
<!--#endif-->
|
||||
<!--#ifdef MP-ALIPAY-->
|
||||
<view v-else :id="n.attrs.id" :class="'_'+n.name+' '+(n.attrs.class||'')" :style="n.attrs.style">
|
||||
<trees :nodes="n.children" />
|
||||
</view>
|
||||
<!--#endif-->
|
||||
<!--#ifndef MP-ALIPAY-->
|
||||
<trees v-else :class="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" :style="n.attrs.style" :nodes="n.children"
|
||||
:lazyLoad="lazyLoad" :loadVideo="loadVideo" />
|
||||
<!--#endif-->
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
<script module="handler" lang="wxs" src="./handler.wxs"></script>
|
||||
<script module="handler" lang="sjs" src="./handler.sjs"></script>
|
||||
<script>
|
||||
global.Parser = {};
|
||||
import trees from './trees'
|
||||
export default {
|
||||
components: {
|
||||
trees
|
||||
},
|
||||
name: 'trees',
|
||||
data() {
|
||||
return {
|
||||
controls: {},
|
||||
// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
|
||||
imgLoad: false,
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
loadVideo: true
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
props: {
|
||||
nodes: Array,
|
||||
// #ifdef MP-WEIXIN || MP-QQ || H5 || APP-PLUS
|
||||
lazyLoad: Boolean,
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
loadVideo: Boolean
|
||||
// #endif
|
||||
},
|
||||
mounted() {
|
||||
// 获取顶层组件
|
||||
this.top = this.$parent;
|
||||
while (this.top.$options.name != 'parser') {
|
||||
if (this.top.top) {
|
||||
this.top = this.top.top;
|
||||
break;
|
||||
}
|
||||
this.top = this.top.$parent;
|
||||
}
|
||||
},
|
||||
// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
|
||||
beforeDestroy() {
|
||||
if (this.observer)
|
||||
this.observer.disconnect();
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
// #ifndef MP-ALIPAY
|
||||
play(e) {
|
||||
if (this.top.videoContexts.length > 1 && this.top.autopause)
|
||||
for (var i = this.top.videoContexts.length; i--;)
|
||||
if (this.top.videoContexts[i].id != e.currentTarget.dataset.id)
|
||||
this.top.videoContexts[i].pause();
|
||||
},
|
||||
// #endif
|
||||
imgtap(e) {
|
||||
var attrs = e.currentTarget.dataset.attrs;
|
||||
if (!attrs.ignore) {
|
||||
var preview = true, data = {
|
||||
id: e.target.id,
|
||||
src: attrs.src,
|
||||
ignore: () => preview = false
|
||||
};
|
||||
global.Parser.onImgtap && global.Parser.onImgtap(data);
|
||||
this.top.$emit('imgtap', data);
|
||||
if (preview) {
|
||||
var urls = this.top.imgList,
|
||||
current = urls[attrs.i] ? parseInt(attrs.i) : (urls = [attrs.src], 0);
|
||||
uni.previewImage({
|
||||
current,
|
||||
urls
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
imglongtap(e) {
|
||||
var attrs = e.item.dataset.attrs;
|
||||
if (!attrs.ignore)
|
||||
this.top.$emit('imglongtap', {
|
||||
id: e.target.id,
|
||||
src: attrs.src
|
||||
})
|
||||
},
|
||||
linkpress(e) {
|
||||
var jump = true,
|
||||
attrs = e.currentTarget.dataset.attrs;
|
||||
attrs.ignore = () => jump = false;
|
||||
global.Parser.onLinkpress && global.Parser.onLinkpress(attrs);
|
||||
this.top.$emit('linkpress', attrs);
|
||||
if (jump) {
|
||||
// #ifdef MP
|
||||
if (attrs['app-id']) {
|
||||
return uni.navigateToMiniProgram({
|
||||
appId: attrs['app-id'],
|
||||
path: attrs.path
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
if (attrs.href) {
|
||||
if (attrs.href[0] == '#') {
|
||||
if (this.top.useAnchor)
|
||||
this.top.navigateTo({
|
||||
id: attrs.href.substring(1)
|
||||
})
|
||||
} else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0) {
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openWeb(attrs.href);
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
uni.setClipboardData({
|
||||
data: attrs.href,
|
||||
success: () =>
|
||||
uni.showToast({
|
||||
title: '链接已复制'
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
} else
|
||||
uni.navigateTo({
|
||||
url: attrs.href
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
error(e) {
|
||||
var context, target = e.currentTarget,
|
||||
source = target.dataset.from;
|
||||
if (source == 'video' || source == 'audio') {
|
||||
// 加载其他 source
|
||||
var index = this.controls[target.id] ? this.controls[target.id].index + 1 : 1;
|
||||
if (index < target.dataset.source.length)
|
||||
this.$set(this.controls, target.id + '.index', index);
|
||||
if (source == 'video') context = uni.createVideoContext(target.id, this);
|
||||
}
|
||||
this.top && this.top.$emit('error', {
|
||||
source,
|
||||
target,
|
||||
errMsg: e.detail.errMsg,
|
||||
errCode: e.detail.errCode,
|
||||
context
|
||||
});
|
||||
},
|
||||
_loadVideo(e) {
|
||||
this.$set(this.controls, e.currentTarget.id, {
|
||||
play: true,
|
||||
index: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 在这里引入自定义样式 */
|
||||
|
||||
/* 链接和图片效果 */
|
||||
._a {
|
||||
display: inline;
|
||||
color: #366092;
|
||||
word-break: break-all;
|
||||
padding: 1.5px 0 1.5px 0;
|
||||
}
|
||||
|
||||
._hover {
|
||||
opacity: 0.7;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
._img {
|
||||
display: inline-block;
|
||||
text-indent: 0;
|
||||
}
|
||||
|
||||
/* #ifdef MP-WEIXIN */
|
||||
:host {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef MP */
|
||||
.interlayer {
|
||||
align-content: inherit;
|
||||
align-items: inherit;
|
||||
display: inherit;
|
||||
flex-direction: inherit;
|
||||
flex-wrap: inherit;
|
||||
justify-content: inherit;
|
||||
width: 100%;
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
._b,
|
||||
._strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
._blockquote,
|
||||
._div,
|
||||
._p,
|
||||
._ol,
|
||||
._ul,
|
||||
._li {
|
||||
display: block;
|
||||
}
|
||||
|
||||
._code {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
._del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
._em,
|
||||
._i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
._h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
._h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
._h3 {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
|
||||
._h5 {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
|
||||
._h6 {
|
||||
font-size: 0.67em;
|
||||
}
|
||||
|
||||
._h1,
|
||||
._h2,
|
||||
._h3,
|
||||
._h4,
|
||||
._h5,
|
||||
._h6 {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
._ins {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
._li {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
._ol-bef {
|
||||
margin-right: 5px;
|
||||
text-align: right;
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
._ul-bef {
|
||||
line-height: normal;
|
||||
margin: 0 12px 0 23px;
|
||||
}
|
||||
|
||||
._ol-bef,
|
||||
._ul_bef {
|
||||
flex: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
._ul-p1 {
|
||||
display: inline-block;
|
||||
height: 0.3em;
|
||||
line-height: 0.3em;
|
||||
overflow: hidden;
|
||||
width: 0.3em;
|
||||
}
|
||||
|
||||
._ul-p2 {
|
||||
border: 0.05em solid black;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
height: 0.23em;
|
||||
width: 0.23em;
|
||||
}
|
||||
|
||||
._q::before {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._q::after {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._sub {
|
||||
font-size: smaller;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
._sup {
|
||||
font-size: smaller;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
/* #ifndef MP-WEIXIN */
|
||||
._abbr,
|
||||
._b,
|
||||
._code,
|
||||
._del,
|
||||
._em,
|
||||
._i,
|
||||
._ins,
|
||||
._label,
|
||||
._q,
|
||||
._span,
|
||||
._strong,
|
||||
._sub,
|
||||
._sup {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef MP-WEIXIN || MP-QQ || MP-ALIPAY */
|
||||
.__bdo,
|
||||
.__bdi,
|
||||
.__ruby,
|
||||
.__rt,
|
||||
._entity {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
._video {
|
||||
background-color: black;
|
||||
display: inline-block;
|
||||
height: 225px;
|
||||
position: relative;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
._video::after {
|
||||
border-color: transparent transparent transparent white;
|
||||
border-style: solid;
|
||||
border-width: 15px 0 15px 30px;
|
||||
content: '';
|
||||
left: 50%;
|
||||
margin: -15px 0 0 -15px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
}
|
||||
</style>
|
||||
263
components/pageFooter/index.vue
Normal file
263
components/pageFooter/index.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<!-- 底部导航 -->
|
||||
<view v-if="showTabBar">
|
||||
<view class="fixed-lb w-full pb-safe z-3000" :style="[bgColor]">
|
||||
<view class="page-footer-wrapper">
|
||||
<view class="page-footer" :class="{
|
||||
'page-footer2': newData.navStyleConfig.tabVal == 1,
|
||||
'page-footer3': newData.navStyleConfig.tabVal == 2,
|
||||
}" id="target" :style="[componentStyle]">
|
||||
<view class="foot-item flex-1 flex-col flex-center h-96 relative" v-for="(item,index) in newData.menuList" :key="index" @click="goRouter(item)">
|
||||
<template v-if="item.link.split('?')[0] == activeRouter">
|
||||
<image v-if="newData.navStyleConfig.tabVal != 1" :src="item.imgList[0]"></image>
|
||||
<view v-if="newData.navStyleConfig.tabVal != 2" class="txt active" :style="[txtActiveColor]">{{item.name}}</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<image v-if="newData.navStyleConfig.tabVal != 1" :src="item.imgList[1]"></image>
|
||||
<view v-if="newData.navStyleConfig.tabVal != 2" class="txt" :style="[txtColor]">{{item.name}}</view>
|
||||
</template>
|
||||
<uni-badge v-if="item.link === '/pages/order_addcart/order_addcart' && cartNum>0" class="uni-badge-left-margin" :text="cartNum" absolute="rightTop">
|
||||
</uni-badge>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view :style="{ height: `${footerHeight}px` }"></view>
|
||||
<view class="pb-safe"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState,mapGetters} from "vuex"
|
||||
import {getCartCounts} from '@/api/order.js';
|
||||
export default {
|
||||
name: 'pageFooter',
|
||||
props: {
|
||||
isTabBar:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
configData:{
|
||||
type: Object,
|
||||
default: ()=>{}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isLogin', 'cartNum','tabBarData']),
|
||||
txtActiveColor() {
|
||||
let styleObject = {};
|
||||
if (this.newData.toneConfig && this.newData.toneConfig.tabVal) {
|
||||
styleObject['color'] = this.newData.activeTxtColor.color[0].item;
|
||||
}
|
||||
return styleObject;
|
||||
},
|
||||
txtColor() {
|
||||
let styleObject = {};
|
||||
if (this.newData.toneConfig && this.newData.toneConfig.tabVal) {
|
||||
styleObject['color'] = this.newData.txtColor.color[0].item;
|
||||
}
|
||||
return styleObject;
|
||||
},
|
||||
bgColor() {
|
||||
let styleObject = {};
|
||||
if (!this.newData.name) {
|
||||
return styleObject;
|
||||
}
|
||||
if (!this.newData.navConfig.tabVal) {
|
||||
styleObject['background'] = this.newData.bgColor.color[0].item;
|
||||
}
|
||||
return styleObject;
|
||||
},
|
||||
componentStyle() {
|
||||
let styleObject = {};
|
||||
let borderRadius = ``;
|
||||
if (!this.newData.name) {
|
||||
return styleObject;
|
||||
}
|
||||
if (this.newData.navConfig.tabVal) {
|
||||
borderRadius = `${this.newData.fillet.val * 2}rpx`;
|
||||
if (this.newData.fillet.type) {
|
||||
borderRadius =
|
||||
`${this.newData.fillet.valList[0].val * 2}rpx ${this.newData.fillet.valList[1].val * 2}rpx ${this.newData.fillet.valList[3].val * 2}rpx ${this.newData.fillet.valList[2].val * 2}rpx`;
|
||||
}
|
||||
styleObject['right'] = `${this.newData.prConfig.val * 2}rpx`;
|
||||
styleObject['bottom'] = `${this.newData.mbConfig.val * 2}rpx`;
|
||||
styleObject['left'] = `${this.newData.prConfig.val * 2}rpx`;
|
||||
styleObject['padding-top'] = `${this.newData.topConfig.val * 2}rpx`;
|
||||
styleObject['padding-bottom'] = `${this.newData.bottomConfig.val * 2}rpx`;
|
||||
styleObject['border-radius'] = borderRadius;
|
||||
styleObject['background'] = this.newData.bgColor2.color[0].item;
|
||||
} else {
|
||||
styleObject['padding-top'] = `${this.newData.topConfig.val * 2}rpx`;
|
||||
styleObject['padding-bottom'] = `${this.newData.bottomConfig.val * 2}rpx`;
|
||||
styleObject['background'] = this.newData.bgColor.color[0].item;
|
||||
}
|
||||
return styleObject;
|
||||
},
|
||||
// validConfigData() {
|
||||
// return this.configData && Object.keys(this.configData).length > 0;
|
||||
// },
|
||||
},
|
||||
watch: {
|
||||
configData(newVal){
|
||||
console.log(newVal,'oo');
|
||||
if(!this.isTabBar && newVal){
|
||||
let configData = newVal;
|
||||
this.newData = configData;
|
||||
this.showTabBar = configData.effectConfig.tabVal;
|
||||
}
|
||||
},
|
||||
tabBarData(newVal){
|
||||
if(newVal){
|
||||
let configData = newVal;
|
||||
if(this.isTabBar){
|
||||
this.newData = configData;
|
||||
}
|
||||
this.showTabBar = configData.effectConfig.tabVal;
|
||||
if (configData.effectConfig.tabVal) {
|
||||
uni.hideTabBar()
|
||||
} else {
|
||||
uni.showTabBar()
|
||||
}
|
||||
this.$emit('newDataStatus', configData.effectConfig.tabVal)
|
||||
}
|
||||
}
|
||||
// 'newData.menuList'(newValue, oldValue) {
|
||||
// this.$nextTick(() => {
|
||||
// const query = uni.createSelectorQuery().in(this);
|
||||
// query.select("#target").boundingClientRect(({
|
||||
// height
|
||||
// }) => {
|
||||
// this.footerHeight = height;
|
||||
// }).exec();
|
||||
// });
|
||||
// }
|
||||
},
|
||||
created() {
|
||||
let routes = getCurrentPages(); //获取当前打开过的页面路由数组
|
||||
let curRoute = routes[routes.length - 1].route //获取当前页面路由
|
||||
this.activeRouter = '/' + curRoute
|
||||
},
|
||||
mounted() {
|
||||
this.navigationInfo();
|
||||
if (this.isLogin) {
|
||||
this.getCartNum()
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newData: {},
|
||||
activeRouter: '',
|
||||
showTabBar: false,
|
||||
footerHeight: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navigationInfo() {
|
||||
//判断渲染来源是一级菜单还是微页面
|
||||
if(this.isTabBar && this.tabBarData.effectConfig){
|
||||
let configData = this.tabBarData;
|
||||
this.newData = configData;
|
||||
this.showTabBar = configData.effectConfig.tabVal;
|
||||
if (configData.effectConfig.tabVal) {
|
||||
uni.hideTabBar()
|
||||
} else {
|
||||
uni.showTabBar()
|
||||
}
|
||||
this.$emit('newDataStatus', configData.effectConfig.tabVal)
|
||||
}
|
||||
|
||||
},
|
||||
goRouter(item) {
|
||||
var pages = getCurrentPages();
|
||||
var page = (pages[pages.length - 1]).$page.fullPath;
|
||||
if (item.link == page) return
|
||||
if (item.link == '/pages/short_video/appSwiper/index' || item.link == '/pages/short_video/nvueSwiper/index') {
|
||||
//#ifdef APP
|
||||
item.link = '/pages/short_video/appSwiper/index'
|
||||
//#endif
|
||||
//#ifndef APP
|
||||
item.link = '/pages/short_video/nvueSwiper/index'
|
||||
//#endif
|
||||
}
|
||||
uni.switchTab({
|
||||
url: item.link,
|
||||
fail(err) {
|
||||
uni.redirectTo({
|
||||
url: item.link
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getCartNum: function() {
|
||||
getCartCounts().then(res => {
|
||||
this.$store.commit('indexData/setCartNum', res.data.count + '')
|
||||
}).catch(err=>{
|
||||
console.log(err.msg);
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-footer-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.z-3000{
|
||||
z-index: 3000;
|
||||
}
|
||||
.page-footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
|
||||
.foot-item image {
|
||||
display: block;
|
||||
height: 40rpx;
|
||||
width: 40rpx;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.foot-item .txt {
|
||||
margin-top: 4rpx;
|
||||
font-size: 20rpx;
|
||||
line-height: 28rpx;
|
||||
color: #333333;
|
||||
|
||||
&.active {
|
||||
color: var(--view-theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-footer2 .foot-item .txt {
|
||||
margin-top: 0;
|
||||
font-size: 32rpx;
|
||||
line-height: 44rpx;
|
||||
color: #333333;
|
||||
|
||||
&.active {
|
||||
color: var(--view-theme);
|
||||
}
|
||||
}
|
||||
|
||||
.page-footer2.float .foot-item::before,
|
||||
.page-footer3.float .foot-item::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 2rpx;
|
||||
height: 32rpx;
|
||||
background: #CCCCCC;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.page-footer2.float .foot-item:first-child::before,
|
||||
.page-footer3.float .foot-item:first-child::before {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
199
components/privacyAgreementPopup/index.vue
Normal file
199
components/privacyAgreementPopup/index.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<view :style="colorStyle">
|
||||
<view class="mask" @touchmove.prevent :hidden="isShow === false"></view>
|
||||
<view class="product-window" :class="{'on':isShow}">
|
||||
<view class="mp-data">
|
||||
<text class="mp-name">服务与隐私协议</text>
|
||||
</view>
|
||||
<view class="trip-msg">
|
||||
<view class="trip">
|
||||
{{`请仔细阅读以下内容,并作出适当的选择:`}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="trip-title">
|
||||
隐私政策概要
|
||||
</view>
|
||||
<view class="trip-msg">
|
||||
<view class="trip">
|
||||
当您点击同意并继续使用产品服务时,即表示您已理解并同意该条款内容,该条款将对您产生法律约束力。如您拒绝,将无法继续下一步操作。
|
||||
</view>
|
||||
</view>
|
||||
<view class="main-color" @click.stop="privacy('privacy')">点击阅读{{agreementName}}</view>
|
||||
<view class="bottom">
|
||||
<button class="save open" type="default" id="agree-btn" open-type="agreePrivacyAuthorization"
|
||||
@agreeprivacyauthorization="handleAgree">同意并继续</button>
|
||||
<button class="reject" @click="rejectAgreement">取消</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import colors from "@/mixins/color";
|
||||
export default {
|
||||
mixins: [colors],
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
agreementName: '',
|
||||
configData: this.$Cache.get('BASIC_CONFIG'),
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
wx.getPrivacySetting({
|
||||
success: res => {
|
||||
if (res.needAuthorization) {
|
||||
// 需要弹出隐私协议
|
||||
this.isShow = true
|
||||
this.agreementName = res.privacyContractName
|
||||
} else {
|
||||
this.$emit('onAgree');
|
||||
// 用户已经同意过隐私协议,所以不需要再弹出隐私协议,也能调用已声明过的隐私接口
|
||||
// wx.getUserProfile()
|
||||
// wx.chooseMedia()
|
||||
// wx.getClipboardData()
|
||||
// wx.startRecord()
|
||||
}
|
||||
},
|
||||
fail: () => {},
|
||||
complete: () => {}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 同意
|
||||
handleAgree() {
|
||||
this.isShow = false
|
||||
this.$emit('onAgree');
|
||||
},
|
||||
// 拒绝
|
||||
rejectAgreement() {
|
||||
this.isShow = false
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
this.$emit('onReject');
|
||||
},
|
||||
// 跳转协议
|
||||
privacy(type) {
|
||||
uni.navigateTo({
|
||||
url: "/pages/users/privacy/index?type=" + type
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.pl-sty {
|
||||
color: #999999;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
.product-window.on {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.mask {
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.product-window {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
z-index: 1000;
|
||||
border-radius: 40rpx 40rpx 0 0;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transition: all .3s cubic-bezier(.25, .5, .5, .9);
|
||||
padding: 64rpx 40rpx;
|
||||
padding-bottom: 38rpx;
|
||||
padding-bottom: calc(38rpx + constant(safe-area-inset-bottom)); ///兼容 IOS<11.2/
|
||||
padding-bottom: calc(38rpx + env(safe-area-inset-bottom)); ///兼容 IOS>11.2/
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
.icon-guanbi {
|
||||
position: absolute;
|
||||
top: 40rpx;
|
||||
right: 40rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.mp-data {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.mp-name {
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
line-height: 48rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.trip-msg {
|
||||
padding-bottom: 32rpx;
|
||||
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.trip {
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
font-family: PingFang SC-Regular, PingFang SC;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.trip-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.main-color {
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
color: var(--view-theme);
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
.save,
|
||||
.reject {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 670rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 80rpx;
|
||||
background-color: #F5F5F5;
|
||||
color: #333;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.save {
|
||||
background-color: var(--view-theme);
|
||||
color: #fff;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
318
components/productConSwiper/index.vue
Normal file
318
components/productConSwiper/index.vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<!-- 商品详情轮播图 -->
|
||||
<view class="product-bg" :style="[swiperHeight]">
|
||||
<swiper
|
||||
:current="currents"
|
||||
:indicator-dots="indicatorDots"
|
||||
indicator-active-color="var(--view-theme)"
|
||||
:autoplay="showSku || videoline || pageHide ? false : true"
|
||||
:circular="circular"
|
||||
:interval="interval"
|
||||
:duration="duration"
|
||||
@change="change"
|
||||
v-if="isPlay"
|
||||
>
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<swiper-item v-if="videoline">
|
||||
<view class="item">
|
||||
<view v-show="!controls" class="item-box">
|
||||
<video
|
||||
id="myVideo"
|
||||
:src="videoline"
|
||||
controls
|
||||
show-center-play-btn
|
||||
show-mute-btn="true"
|
||||
auto-pause-if-navigate
|
||||
:custom-cache="false"
|
||||
:enable-progress-gesture="false"
|
||||
:poster="imgUrls[0]"
|
||||
@pause="videoPause"
|
||||
class="w-full h-full"
|
||||
></video>
|
||||
</view>
|
||||
<view class="poster" v-show="controls">
|
||||
<image class="image" :src="imgUrls[0]" @tap="getpreviewImage(0)"></image>
|
||||
</view>
|
||||
<view class="stop" v-show="controls" @tap="bindPause">
|
||||
<image class="image" src="../../static/images/stop.png"></image>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<swiper-item v-if="videoline">
|
||||
<view class="item">
|
||||
<view class="poster" v-show="controls">
|
||||
<image class="image" :src="imgUrls[0]" @tap="getpreviewImage(0)"></image>
|
||||
</view>
|
||||
<view class="stop" v-show="controls" @tap="bindPause">
|
||||
<image class="image" src="../../static/images/stop.png"></image>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
<!-- #endif -->
|
||||
<block v-for="(item, index) in imgUrls" :key="index">
|
||||
<swiper-item v-if="videoline ? index >= 1 : index >= 0">
|
||||
<image :src="item" class="slide-image" :mode="!autoHeight ? 'aspectFit' : ''"
|
||||
@tap="getpreviewImage(index)"/>
|
||||
</swiper-item>
|
||||
</block>
|
||||
</swiper>
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view v-if="!isPlay" style="width: 100%; height: 750rpx">
|
||||
<video
|
||||
id="myVideo"
|
||||
:src="videoline"
|
||||
controls
|
||||
show-center-play-btn
|
||||
show-mute-btn="true"
|
||||
autoplay="true"
|
||||
auto-pause-if-navigate
|
||||
:custom-cache="false"
|
||||
:enable-progress-gesture="false"
|
||||
:poster="imgUrls[0]"
|
||||
class="w-full h-full"
|
||||
@pause="videoPause"
|
||||
></video>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view class="w-full flex-center absolute left-0 bottom-86rpx z-20" v-if="showSku">
|
||||
<view class="sku-desc flex-center px-20 fs-20 text--w111-fff line1">{{sku}}</view>
|
||||
</view>
|
||||
<view class="w-full flex-center absolute left-0 bottom-34rpx z-20" v-if="imgUrls.length > 1 && showDot && controls">
|
||||
<block v-for="(_, index) in imgUrls" :key="index">
|
||||
<view class="dot_item h-4 rd-2rpx" :style="{ 'background-color': currents == index ? '#fff' : 'rgba(255,255,255,0.4)', width: dotWidth + 'rpx' }"></view>
|
||||
</block>
|
||||
</view>
|
||||
<view class="w-full flex-center abs-lb h-100 white-jianbian" v-if="controls"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
currents: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
imgUrls: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
videoline: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showDot: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
autoHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
sku:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showSku:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
pageHide:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
indicatorDots: false,
|
||||
circular: true,
|
||||
interval: 3000,
|
||||
duration: 500,
|
||||
// currents: 0,
|
||||
controls: true,
|
||||
isPlay: true,
|
||||
videoContext: '',
|
||||
imgHeight:375
|
||||
};
|
||||
},
|
||||
watch:{
|
||||
currents(val){
|
||||
if(val != 0){
|
||||
this.controls = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.videoline) {
|
||||
this.imgUrls.shift();
|
||||
}
|
||||
// #ifndef APP-PLUS
|
||||
this.videoContext = uni.createVideoContext('myVideo', this);
|
||||
// #endif
|
||||
},
|
||||
computed: {
|
||||
dotWidth() {
|
||||
let windowWidth = uni.getWindowInfo().windowWidth;
|
||||
return (windowWidth * 2 - (40 + (this.imgUrls.length - 1) * 12)) / this.imgUrls.length;
|
||||
},
|
||||
swiperHeight() {
|
||||
let windowWidth = uni.getWindowInfo().windowWidth;
|
||||
let that = this;
|
||||
if(this.autoHeight){
|
||||
if(this.videoline){
|
||||
return {
|
||||
height: '750rpx'
|
||||
};
|
||||
}else{
|
||||
uni.getImageInfo({
|
||||
src: this.imgUrls[0],
|
||||
success: (image) => {
|
||||
that.imgHeight = image.height * windowWidth / image.width;
|
||||
}
|
||||
});
|
||||
return {
|
||||
height: `${this.imgHeight}px`
|
||||
};
|
||||
}
|
||||
|
||||
}else{
|
||||
return {
|
||||
height: '750rpx'
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getpreviewImage(index) {
|
||||
if(this.showSku){
|
||||
this.$emit('previewImageOpen')
|
||||
}else{
|
||||
uni.previewImage({
|
||||
urls: this.imgUrls,
|
||||
current: this.imgUrls[index]
|
||||
});
|
||||
}
|
||||
},
|
||||
videoPause(e) {
|
||||
// #ifdef APP-PLUS
|
||||
this.isPlay = true;
|
||||
this.autoplay = true;
|
||||
// #endif
|
||||
},
|
||||
bindPause: function () {
|
||||
// #ifndef APP-PLUS
|
||||
this.videoContext.play();
|
||||
this.$set(this, 'controls', false);
|
||||
this.autoplay = false;
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
this.isPlay = false;
|
||||
this.videoContext = uni.createVideoContext('myVideo', this);
|
||||
this.videoContext.play();
|
||||
// #endif
|
||||
},
|
||||
change(e) {
|
||||
this.$emit('attrPicIndex',e.detail.current)
|
||||
if(this.showSku){
|
||||
this.$emit('changeAttrPic',e.detail.current)
|
||||
}
|
||||
// this.$set(this, 'currents', e.detail.current);
|
||||
// this.$emit('onChange',e.detail.current);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.item-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgb(74, 74, 76);
|
||||
}
|
||||
|
||||
.product-bg {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-bg swiper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-bg .slide-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.product-bg .pages {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
height: 34rpx;
|
||||
padding: 0 10rpx;
|
||||
border-radius: 3rpx;
|
||||
right: 30rpx;
|
||||
bottom: 30rpx;
|
||||
line-height: 34rpx;
|
||||
font-size: 24rpx;
|
||||
color: #050505;
|
||||
}
|
||||
|
||||
.product-bg .item {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.product-bg .item .poster {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 750rpx;
|
||||
width: 100%;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.product-bg .item .poster .image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.product-bg .item .stop {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 136rpx;
|
||||
height: 136rpx;
|
||||
margin-top: -68rpx;
|
||||
margin-left: -68rpx;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.product-bg .item .stop .image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dot_item ~ .dot_item {
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
.white-jianbian {
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.2) 100%);
|
||||
}
|
||||
.bottom-86rpx{
|
||||
bottom: 86rpx;
|
||||
}
|
||||
.bottom-34rpx{
|
||||
bottom: 34rpx;
|
||||
}
|
||||
.sku-desc{
|
||||
// width: 456rpx;
|
||||
height: 40rpx;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
</style>
|
||||
543
components/productWindow/index.vue
Normal file
543
components/productWindow/index.vue
Normal file
@@ -0,0 +1,543 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="product-window"
|
||||
:class="(attr.cartAttr === true ? 'on' : '') + ' ' + (iSbnt?'join':'') + ' ' + (iScart?'joinCart':'')" @touchmove.stop.prevent="moveHandle"
|
||||
>
|
||||
<view class="textpic acea-row" >
|
||||
<view class="pictrue" @click="showImg()">
|
||||
<image :src="attr.productSelect.image"></image>
|
||||
<view class="icon flex-center" v-if="fangda">
|
||||
<view class="iconfont icon-ic_enlarge"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="pl-24 pt-24 flex-col">
|
||||
<view class="flex-y-center" v-if="attr.productSelect.integral">
|
||||
<image src="../../static/img/mall05.png" class="w-32 h-32"></image>
|
||||
<text class="lh-40rpx font-num fs-40 SemiBold pl-8">{{attr.productSelect.integral}}</text>
|
||||
<text class="fs-28 lh-40rpx px-8 text--w111-666">+</text>
|
||||
<baseMoney :money="attr.productSelect.price" symbolSize="28" integerSize="40" decimalSize="28" color="var(--view-theme)" weight></baseMoney>
|
||||
</view>
|
||||
<baseMoney
|
||||
:money="attr.productSelect.price"
|
||||
symbolSize="32"
|
||||
integerSize="48"
|
||||
decimalSize="32"
|
||||
incolor="var(--primary-theme-con)"
|
||||
weight v-else></baseMoney>
|
||||
<view class="inline-block h-48 lh-48rpx text-center rd-24rpx bg-color fs-24 text--w111-fff px-20 mt-16"
|
||||
v-if="type == 0">
|
||||
预估到手 <text class="fs-28 fw-600 pl-8">¥{{attr.productSelect.pay_price}}</text>
|
||||
</view>
|
||||
<view class="mt-12 fs-24 text--w111-999" v-if="type == 1 || type == 3">限量:{{ attr.productSelect.quota_show || 0 }}</view>
|
||||
<view class="mt-12 fs-24 text--w111-999" v-else>库存:{{ attr.productSelect.stock || attr.productSelect.product_stock || 0 }}</view>
|
||||
</view>
|
||||
<view class="iconfont icon-ic_close1" @click="closeAttr"></view>
|
||||
</view>
|
||||
<view class="mt-36">
|
||||
<scroll-view scroll-y="true" :style="'max-height: '+windowHeight+'rpx'" >
|
||||
<view class="productWinList">
|
||||
<view class="item" v-for="(item, indexw) in attr.productAttr" :key="indexw">
|
||||
<view v-show="item.is_pic == 0">
|
||||
<view class="fs-28 fw-500 px-32">{{ item.attr_name }}</view>
|
||||
<view class="listn acea-row row-middle">
|
||||
<view class="itemn" :class="item.index === itemn.attr ? 'active' : ''"
|
||||
v-for="(itemn, indexn) in item.attr_value" @click="tapAttr(indexw, indexn)"
|
||||
:key="indexn">
|
||||
{{ itemn.attr }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-show="item.is_pic == 1">
|
||||
<view class="flex-between-center">
|
||||
<view class="fs-28 fw-500 px-32">{{ item.attr_name }}</view>
|
||||
<view class="pr-32 fs-24 text--w111-666 flex-y-center" v-show="gridShow == 1" @tap="toggleGridAttr(0)">
|
||||
<text class="iconfont icon-a-ic_Imageandtextsorting fs-28"></text>
|
||||
<text class="pl-6">列表</text>
|
||||
</view>
|
||||
<view class="pr-32 fs-24 text--w111-666 flex-y-center" v-show="gridShow == 0" @tap="toggleGridAttr(1)">
|
||||
<text class="iconfont icon-a-ic_Picturearrangement fs-28"></text>
|
||||
<text class="pl-6">宫格</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="pl-32 mt-32" v-show="gridShow == 1">
|
||||
<scroll-view scroll-x="true" class="white-nowrap vertical-middle w-686"
|
||||
show-scrollbar="false">
|
||||
<view class="inline-block mr-12"
|
||||
v-for="(itemn, indexn) in item.attr_value"
|
||||
:key="indexn"
|
||||
@click="tapAttr(indexw, indexn)">
|
||||
<view class="grid-item-box" :class="item.index === itemn.attr ? 'grid-active' : ''">
|
||||
<view class="w-full h-192 relative">
|
||||
<image class="w-full h-192 block" :src="itemn.pic" mode="aspectFill"></image>
|
||||
<view class="proview-icon flex-center" @tap.stop="proviewImg(itemn.pic)">
|
||||
<text class="iconfont icon-ic_enlarge fs-24 text--w111-fff"></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-1 bg--w111-f5f5f5 tname flex-center fs-24">{{itemn.attr}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view class="listn acea-row row-middle" v-show="gridShow == 0">
|
||||
<view class="cell-itemn flex-between-center"
|
||||
:class="item.index === itemn.attr ? 'active' : ''"
|
||||
v-for="(itemn, indexn) in item.attr_value" :key="indexn"
|
||||
@click="tapAttr(indexw, indexn)">
|
||||
<image :src="itemn.pic" class="w-48 h-48 rd-50-p111-"></image>
|
||||
<text class="pl-8">{{ itemn.attr }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="cart px-30 flex-between-center mt-24" v-if="type != 'setMeal' && type !='points'">
|
||||
<view class="fs-28">数量</view>
|
||||
<view class="carnum flex-y-center">
|
||||
<template v-if="type == 0">
|
||||
<text class="font-num fs-22" v-if="storeInfo.min_qty && !storeInfo.is_limit">(商品{{storeInfo.min_qty}}{{storeInfo.unit_name}}起购)</text>
|
||||
<text class="font-num fs-22" v-else-if="!storeInfo.min_qty && storeInfo.is_limit && storeInfo.limit_num > 0">(商品限购{{storeInfo.limit_num}}{{storeInfo.unit_name}})</text>
|
||||
<text class="font-num fs-22" v-else-if="storeInfo.min_qty && storeInfo.is_limit && storeInfo.limit_num > 0">(商品起购{{storeInfo.min_qty}}{{storeInfo.unit_name}} 限购{{storeInfo.limit_num}}{{storeInfo.unit_name}})</text>
|
||||
</template>
|
||||
<view class="item reduce flex-center"
|
||||
:class="{'on': reduceDisabled}" @click="CartNumDes">
|
||||
<text class="iconfont icon-ic_Reduce fs-24"></text>
|
||||
</view>
|
||||
<view class='item num flex-y-center'>
|
||||
<input type="number" :disabled="isDisabled" v-model="attr.productSelect.cart_num"
|
||||
@input="bindCode(attr.productSelect.cart_num)"></input>
|
||||
</view>
|
||||
<view class="item plus flex-center"
|
||||
:class="{'on': addDisabled }"
|
||||
@click="CartNumAdd">
|
||||
<text class="iconfont icon-ic_increase fs-24"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isExtends">
|
||||
<view class="joinBnt bg-gray" v-if="attr.productSelect.stock <= 0">已售罄</view>
|
||||
<view class="joinBnt bg-color" v-else @click="cartConfirm">确定</view>
|
||||
</view>
|
||||
<view class="joinBnt bg-color"
|
||||
v-if="iSbnt && attr.productSelect.product_stock>0 &&attr.productSelect.quota>0" @click="goCat">我要参团
|
||||
</view>
|
||||
<view class="joinBnt on"
|
||||
v-else-if="(iSbnt && attr.productSelect.quota<=0)||(iSbnt &&attr.productSelect.product_stock<=0)">已售罄
|
||||
</view>
|
||||
<view class="joinBnt bg-color" v-if="iScart && attr.productSelect.stock" @click="goCat">确定</view>
|
||||
<view class="joinBnt on" v-else-if="iScart && !attr.productSelect.stock">已售罄</view>
|
||||
</view>
|
||||
<view class="mask z-25" @touchmove.stop.prevent="moveHandle" :hidden="attr.cartAttr === false" @click="closeAttr"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let windowHeight = (uni.getWindowInfo().windowHeight*(3/4)-238)*2;
|
||||
export default {
|
||||
|
||||
props: {
|
||||
attr: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
storeInfo: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
limitNum: {
|
||||
type: Number,
|
||||
value: 0
|
||||
},
|
||||
isShow: {
|
||||
type: Number,
|
||||
value: 0
|
||||
},
|
||||
iSbnt: {
|
||||
type: Number,
|
||||
value: 0
|
||||
},
|
||||
iSplus: {
|
||||
type: Number,
|
||||
value: 0
|
||||
},
|
||||
iScart: {
|
||||
type: Number,
|
||||
value: 0
|
||||
},
|
||||
is_vip: {
|
||||
type: Number,
|
||||
value: 0
|
||||
},
|
||||
type: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
fangda: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isExtends:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showFooter:{
|
||||
type:Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
windowHeight:windowHeight,
|
||||
gridShow: 1,
|
||||
};
|
||||
},
|
||||
computed:{
|
||||
reduceDisabled(){
|
||||
if(this.type == 0 && this.storeInfo && this.storeInfo.min_qty){
|
||||
return this.storeInfo.min_qty == this.attr.productSelect.cart_num
|
||||
}else{
|
||||
return this.attr.productSelect.cart_num <= 1
|
||||
}
|
||||
},
|
||||
addDisabled(){
|
||||
if(this.type == 0 && this.storeInfo && this.storeInfo.is_limit && this.storeInfo.limit_num){
|
||||
return this.storeInfo.limit_num == this.attr.productSelect.cart_num
|
||||
}else{
|
||||
return this.attr.productSelect.cart_num >= this.attr.productSelect.stock;
|
||||
}
|
||||
},
|
||||
isDisabled(){
|
||||
if(this.type ==0 && (this.storeInfo.min_qty || this.storeInfo.limit_num)){
|
||||
return true
|
||||
}else false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleGridAttr(type){
|
||||
this.gridShow = type;
|
||||
},
|
||||
goCat() {
|
||||
this.$emit('goCat');
|
||||
},
|
||||
bindCode(e) {
|
||||
this.$emit('iptCartNum', this.attr.productSelect.cart_num);
|
||||
},
|
||||
closeAttr() {
|
||||
this.$emit('myevent');
|
||||
},
|
||||
CartNumDes() {
|
||||
if(this.reduceDisabled) return
|
||||
this.$emit('ChangeCartNum', false);
|
||||
},
|
||||
CartNumAdd() {
|
||||
if(this.addDisabled) return
|
||||
this.$emit('ChangeCartNum', true);
|
||||
},
|
||||
tapAttr: function(indexw, indexn) {
|
||||
let that = this;
|
||||
that.$emit("attrVal", {
|
||||
indexw: indexw,
|
||||
indexn: indexn
|
||||
});
|
||||
this.$set(this.attr.productAttr[indexw], 'index', this.attr.productAttr[indexw].attr_values[indexn]);
|
||||
let value = that
|
||||
.getCheckedValue()
|
||||
.join(",");
|
||||
that.$emit("ChangeAttr", value);
|
||||
|
||||
},
|
||||
moveHandle() {},
|
||||
//获取被选中属性;
|
||||
getCheckedValue: function() {
|
||||
let productAttr = this.attr.productAttr;
|
||||
let value = [];
|
||||
for (let i = 0; i < productAttr.length; i++) {
|
||||
for (let j = 0; j < productAttr[i].attr_values.length; j++) {
|
||||
if (productAttr[i].index === productAttr[i].attr_values[j]) {
|
||||
value.push(productAttr[i].attr_values[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
showImg() {
|
||||
this.$emit('getImg');
|
||||
},
|
||||
cartConfirm(){
|
||||
this.$emit('onConfirm');
|
||||
},
|
||||
proviewImg(img){
|
||||
uni.previewImage({
|
||||
current: 0,
|
||||
urls: [img]
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.vip-money {
|
||||
color: #282828;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.vipImg {
|
||||
width: 56rpx;
|
||||
height: 20rpx;
|
||||
margin-left: 6rpx;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.product-window {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
background-color: #fff;
|
||||
z-index: 100;
|
||||
border-radius: 40rpx 40rpx 0 0;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transition: all 0.3s ease-in-out;
|
||||
padding-bottom: constant(safe-area-inset-bottom); ///兼容 IOS<11.2/
|
||||
padding-bottom: env(safe-area-inset-bottom);///兼容 IOS>11.2/
|
||||
}
|
||||
|
||||
.product-window.on {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.product-window.join {
|
||||
padding-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.product-window.joinCart {
|
||||
padding-bottom: 30rpx;
|
||||
z-index: 4000;
|
||||
}
|
||||
|
||||
.product-window .textpic {
|
||||
padding: 0 32rpx;
|
||||
margin-top: 48rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-window .textpic .pictrue {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
position: relative;
|
||||
.icon{
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
border-radius: 4rpx;
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
right: 8rpx;
|
||||
text-align: center;
|
||||
line-height: 23rpx;
|
||||
.iconfont{
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-window .textpic .pictrue image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.product-window .textpic .text {
|
||||
width: 410rpx;
|
||||
font-size: 32rpx;
|
||||
color: #202020;
|
||||
}
|
||||
|
||||
.product-window .textpic .text .money {
|
||||
font-size: 24rpx;
|
||||
margin-top: 40rpx;
|
||||
.icon{
|
||||
display: inline-block;
|
||||
font-size: 16rpx;
|
||||
font-weight: normal;
|
||||
background: #FF9500;
|
||||
color: #fff;
|
||||
border-radius: 18rpx;
|
||||
padding: 2rpx 6rpx;
|
||||
margin-left: 10rpx;
|
||||
.iconfont{
|
||||
font-size: 16rpx;
|
||||
margin-right: 4rpx;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.product-window .textpic .icon-ic_close1 {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: -5rpx;
|
||||
font-size: 35rpx;
|
||||
color: #8a8a8a;
|
||||
}
|
||||
.product-window .productWinList .item~.item {
|
||||
margin-top: 36rpx;
|
||||
}
|
||||
|
||||
.product-window .productWinList .item .listn {
|
||||
padding: 0 32rpx 0 16rpx;
|
||||
}
|
||||
|
||||
.product-window .productWinList .item .listn .itemn {
|
||||
// height: 56rpx;
|
||||
line-height: 56rpx;
|
||||
border: 1rpx solid #F2F2F2;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 28rpx;
|
||||
margin: 24rpx 0 0 16rpx;
|
||||
background-color: #F2F2F2;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.product-window .productWinList .item .active {
|
||||
color: var(--view-theme) !important;
|
||||
background: var(--view-minorColorT) !important;
|
||||
border-color: var(--view-theme) !important;
|
||||
}
|
||||
|
||||
.product-window .productWinList .item .listn .itemn.limit {
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.product-window .cart .carnum view {
|
||||
width: 84rpx;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
line-height: 54rpx;
|
||||
color: #282828;
|
||||
font-size: 45rpx;
|
||||
}
|
||||
|
||||
.product-window .cart .carnum .reduce {
|
||||
border-right: 0;
|
||||
border-radius: 6rpx 0 0 6rpx;
|
||||
line-height: 48rpx;
|
||||
font-size: 60rpx;
|
||||
}
|
||||
|
||||
.product-window .cart .carnum .reduce.on {
|
||||
// border-color: #e3e3e3;
|
||||
color: #DEDEDE;
|
||||
}
|
||||
|
||||
.product-window .cart .carnum .plus {
|
||||
border-left: 0;
|
||||
border-radius: 0 6rpx 6rpx 0;
|
||||
line-height: 46rpx;
|
||||
}
|
||||
|
||||
.product-window .cart .carnum .plus.on {
|
||||
// border-color: #e3e3e3;
|
||||
color: #dedede;
|
||||
}
|
||||
|
||||
.product-window .cart .carnum .num {
|
||||
background: rgba(242, 242, 242, 1);
|
||||
color: #282828;
|
||||
font-size: 28rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.product-window .joinBnt {
|
||||
font-size: 28rpx;
|
||||
width: 710rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
text-align: center;
|
||||
line-height: 80rpx;
|
||||
color: #fff;
|
||||
margin: 74rpx auto 12rpx;
|
||||
}
|
||||
|
||||
.product-window .joinBnt.on {
|
||||
background-color: #bbb;
|
||||
color: #fff;
|
||||
}
|
||||
.mt-74{
|
||||
margin-top: 74rpx;
|
||||
}
|
||||
.join_cart{
|
||||
background-color: var(--view-bntColor);
|
||||
}
|
||||
.bg-gray{
|
||||
background-color: #CCCCCC;
|
||||
}
|
||||
.z-25{
|
||||
z-index: 25;
|
||||
}
|
||||
.w-686{
|
||||
width: 686rpx;
|
||||
}
|
||||
.grid-item-box{
|
||||
width: 196rpx;
|
||||
border: 2rpx solid #F5F5F5;
|
||||
border-radius: 8rpx;
|
||||
image{
|
||||
border-radius: 8rpx 8rpx 0 0;
|
||||
}
|
||||
.tname{
|
||||
width: 196rpx;
|
||||
border-radius: 0 0 8rpx 8rpx ;
|
||||
padding: 4rpx 12rpx;
|
||||
height: 74rpx;
|
||||
display: -webkit-box; /* 旧版弹性盒子 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2; /* 显示两行 */
|
||||
-webkit-box-orient: vertical; /* 垂直方向排列 */
|
||||
word-break: break-all; /* 允许单词断行 */
|
||||
white-space: normal; /* 允许文字折行 */
|
||||
}
|
||||
}
|
||||
.grid-active{
|
||||
border: 2rpx solid var(--view-theme);
|
||||
.tname{
|
||||
color: var(--view-theme);
|
||||
background: var(--view-minorColorT);
|
||||
}
|
||||
}
|
||||
.cell-itemn{
|
||||
line-height: 56rpx;
|
||||
border: 1rpx solid #F2F2F2;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
padding: 0 20rpx 0 4rpx;
|
||||
border-radius: 30rpx;
|
||||
margin: 24rpx 0 0 16rpx;
|
||||
background-color: #F2F2F2;
|
||||
word-break: break-all;
|
||||
}
|
||||
.proview-icon{
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 8rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
background: #DDDDDD;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
49
components/recommend/index.vue
Normal file
49
components/recommend/index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<!-- 热门推荐 -->
|
||||
<view class='pt-40' :style="colorStyle">
|
||||
<view class="flex-center">
|
||||
<image class="w-42 h-36" src="../../static/img/recommend_zs.png"></image>
|
||||
<text class="fs-32 fw-500 text--w111-333 lh-44rpx px-6">{{ title }}</text>
|
||||
<image class="w-42 h-36" src="../../static/img/recommend_zs.png"></image>
|
||||
</view>
|
||||
<view class="relative mt-24">
|
||||
<view class="jinabian"></view>
|
||||
<waterfallsFlow ref="waterfallsFlow" :wfList="hostProduct" :showCart="false" recommend @itemTap="goDetail"></waterfallsFlow>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapGetters} from "vuex";
|
||||
import { goShopDetail } from '@/libs/order.js'
|
||||
import colors from "@/mixins/color";
|
||||
import waterfallsFlow from "@/components/WaterfallsFlow/WaterfallsFlow.vue";
|
||||
export default {
|
||||
props: {
|
||||
hostProduct: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '猜你喜欢'
|
||||
},
|
||||
},
|
||||
components: {
|
||||
waterfallsFlow,
|
||||
},
|
||||
mixins: [colors],
|
||||
computed: mapGetters(['uid']),
|
||||
methods: {
|
||||
goDetail(item){
|
||||
goShopDetail(item, this.uid).catch(res => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/goods_details/index?id=${item.id}`
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
198
components/skeleton/index.vue
Normal file
198
components/skeleton/index.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<view v-if="show"
|
||||
:style="{width: systemInfo.width + 'px', height: systemInfo.height + 'px', backgroundColor: bgcolor, position: 'absolute', left: 0, top: 0, zIndex: 9998}">
|
||||
<view v-for="(item,rect_idx) in skeletonRectLists" :key="item.rect"
|
||||
:class="[loading == 'chiaroscuro' ? 'chiaroscuro' : '']"
|
||||
:style="{width: item.width + 'px', height: item.height + 'px', backgroundColor: 'rgb(194, 207, 214,.3)', position: 'absolute', left: item.left + 'px', top: item.top + 'px'}">
|
||||
</view>
|
||||
<view v-for="(item,circle_idx) in skeletonCircleLists" :key="item.circle"
|
||||
:class="loading == 'chiaroscuro' ? 'chiaroscuro' : ''"
|
||||
:style="{width: item.width + 'px', height: item.height + 'px', backgroundColor: 'rgb(194, 207, 214,.3)', borderRadius: item.width + 'px', position: 'absolute', left: item.left + 'px', top: item.top + 'px'}">
|
||||
</view>
|
||||
<view class="spinbox" v-if="loading == 'spin'">
|
||||
<view class="spin"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "skeleton",
|
||||
props: {
|
||||
bgcolor: {
|
||||
type: String,
|
||||
value: '#FFF'
|
||||
},
|
||||
selector: {
|
||||
type: String,
|
||||
value: 'skeleton'
|
||||
},
|
||||
loading: {
|
||||
type: String,
|
||||
value: 'spin'
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
isNodes: {
|
||||
type: Number,
|
||||
value: false
|
||||
} //控制什么时候开始抓取元素节点,只要数值改变就重新抓取
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loadingAni: ['spin', 'chiaroscuro'],
|
||||
systemInfo: {},
|
||||
skeletonRectLists: [],
|
||||
skeletonCircleLists: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isNodes(val) {
|
||||
this.readyAction();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.attachedAction();
|
||||
},
|
||||
methods: {
|
||||
attachedAction: function() {
|
||||
//默认的首屏宽高,防止内容闪现
|
||||
const systemInfo = uni.getWindowInfo();
|
||||
this.systemInfo = {
|
||||
width: systemInfo.windowWidth,
|
||||
height: systemInfo.windowHeight
|
||||
};
|
||||
this.loading = this.loadingAni.includes(this.loading) ? this.loading : 'spin';
|
||||
},
|
||||
readyAction: function() {
|
||||
const that = this;
|
||||
//绘制背景
|
||||
uni.createSelectorQuery().selectAll(`.${this.selector}`).boundingClientRect().exec(function(res) {
|
||||
if(res[0].length>0)
|
||||
that.systemInfo.height = res[0][0].height + res[0][0].top;
|
||||
});
|
||||
|
||||
//绘制矩形
|
||||
this.rectHandle();
|
||||
|
||||
//绘制圆形
|
||||
this.radiusHandle();
|
||||
},
|
||||
rectHandle: function() {
|
||||
const that = this;
|
||||
|
||||
//绘制不带样式的节点
|
||||
uni.createSelectorQuery().selectAll(`.${this.selector}-rect`).boundingClientRect().exec(function(res) {
|
||||
let obj = res[0];
|
||||
obj.forEach((item,index)=>{
|
||||
item.rect = 'rect'+index
|
||||
})
|
||||
that.skeletonRectLists = obj;
|
||||
});
|
||||
|
||||
},
|
||||
radiusHandle() {
|
||||
const that = this;
|
||||
|
||||
uni.createSelectorQuery().selectAll(`.${this.selector}-radius`).boundingClientRect().exec(function(res) {
|
||||
let obj = res[0];
|
||||
obj.forEach((item,index)=>{
|
||||
item.circle = 'circle'+index
|
||||
})
|
||||
that.skeletonCircleLists = obj;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.spinbox {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 9999
|
||||
}
|
||||
|
||||
.spin {
|
||||
display: inline-block;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.spin:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
margin: 1rpx;
|
||||
border-radius: 50%;
|
||||
border: 5rpx solid #409eff;
|
||||
border-color: #409eff transparent #409eff transparent;
|
||||
animation: spin 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.chiaroscuro {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgb(194, 207, 214);
|
||||
animation-duration: 2s;
|
||||
animation-name: blink;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: .4;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes flush {
|
||||
0% {
|
||||
left: -100%;
|
||||
}
|
||||
|
||||
50% {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.shine {
|
||||
animation: flush 2s linear infinite;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background: linear-gradient(to left,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, .85) 50%,
|
||||
rgba(255, 255, 255, 0) 100%)
|
||||
}
|
||||
</style>
|
||||
264
components/splitOrder/index.vue
Normal file
264
components/splitOrder/index.vue
Normal file
@@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<!-- 拆单订单 -->
|
||||
<view class='splitOrder' v-if="splitGoods.length">
|
||||
<view class="all" v-if="select_all">
|
||||
<checkbox-group @change="checkboxAllChange">
|
||||
<checkbox value="all" :checked="isAllSelect" />
|
||||
<text class='checkAll'>全选</text>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
<checkbox-group @change="checkboxChange">
|
||||
<block v-for="(item,index) in splitGoods" :key="index">
|
||||
<view class='items acea-row row-between-wrapper'>
|
||||
<!-- #ifndef MP -->
|
||||
<checkbox :value="(item.id).toString()" :checked="item.checked"
|
||||
color="#ffffff" backgroundColor="#ffffff"
|
||||
activeBackgroundColor="#2A7EFB" activeBorderColor="#2A7EFB"/>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP -->
|
||||
<checkbox :value="item.id" :checked="item.checked" />
|
||||
<!-- #endif -->
|
||||
<view class='picTxt acea-row row-between-wrapper'>
|
||||
<view class='pictrue'>
|
||||
<image :src='item.cart_info.productInfo.image'></image>
|
||||
</view>
|
||||
<view class='text'>
|
||||
<view class="acea-row row-between-wrapper">
|
||||
<view class='name line1'>{{item.cart_info.productInfo.store_name}}</view>
|
||||
<!-- <view>×{{item.cart_num}}</view> -->
|
||||
</view>
|
||||
<view class='infor line1'>
|
||||
属性:{{item.cart_info.productInfo.attrInfo.suk || '默认'}}</view>
|
||||
<view class="acea-row row-middle money-section">
|
||||
实付款:<view class='money'>¥{{item.cart_info.sum_true_price}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class='carnum acea-row row-center-wrapper'>
|
||||
<view class="reduce iconfont icon-ic_Reduce" :class="item.surplus_num == 1 ? 'on' : ''" @click.stop='subCart(item)'></view>
|
||||
<view class='num'>{{item.surplus_num}}</view>
|
||||
<view class="plus iconfont icon-ic_increase" :class="item.surplus_num == item.numShow ? 'on' : ''" @click.stop='addCart(item)'></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
splitGoods: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
select_all: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isAllSelect: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
subCart(item) {
|
||||
item.surplus_num = Number(item.surplus_num) - 1;
|
||||
if (item.surplus_num <= 1) {
|
||||
item.surplus_num = 1
|
||||
}
|
||||
this.$emit('getList', this.splitGoods);
|
||||
},
|
||||
addCart(item) {
|
||||
item.surplus_num = Number(item.surplus_num) + 1;
|
||||
if (item.surplus_num >= item.numShow) {
|
||||
item.surplus_num = item.numShow
|
||||
}
|
||||
this.$emit('getList', this.splitGoods);
|
||||
},
|
||||
inArray: function(search, array) {
|
||||
for (let i in array) {
|
||||
if (array[i] == search) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
checkboxChange(event) {
|
||||
let idList = event.detail.value;
|
||||
this.splitGoods.forEach((item) => {
|
||||
if (this.inArray(item.id, idList)) {
|
||||
item.checked = true;
|
||||
} else {
|
||||
item.checked = false;
|
||||
}
|
||||
})
|
||||
this.$emit('getList', this.splitGoods);
|
||||
if (idList.length == this.splitGoods.length) {
|
||||
this.isAllSelect = true;
|
||||
} else {
|
||||
this.isAllSelect = false;
|
||||
}
|
||||
},
|
||||
forGoods(val) {
|
||||
let that = this;
|
||||
if (!that.splitGoods.length) return
|
||||
that.splitGoods.forEach((item) => {
|
||||
if (val) {
|
||||
item.checked = true;
|
||||
} else {
|
||||
item.checked = false;
|
||||
}
|
||||
})
|
||||
that.$emit('getList', that.splitGoods);
|
||||
},
|
||||
checkboxAllChange(event) {
|
||||
let value = event.detail.value;
|
||||
if (value.length) {
|
||||
this.forGoods(1)
|
||||
} else {
|
||||
this.forGoods(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/deep/ checkbox .wx-checkbox-input.wx-checkbox-input-checked {
|
||||
border: 1px solid $primary-admin !important;
|
||||
background-color: $primary-admin !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
.splitOrder {
|
||||
width: 710rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin: 24rpx auto 0 auto;
|
||||
padding: 32rpx 26rpx;
|
||||
}
|
||||
|
||||
.splitOrder .all {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.splitOrder .all .checkAll {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items~.items {
|
||||
margin-top: 48rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt {
|
||||
width: 604rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .name {
|
||||
width: 444rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .pictrue {
|
||||
width: 136rpx;
|
||||
height: 136rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .pictrue image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .text {
|
||||
width: 450rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .text .reColor {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .text .reElection {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .text .reElection .title {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .text .reElection .reBnt {
|
||||
width: 120rpx;
|
||||
height: 46rpx;
|
||||
border-radius: 23rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .text .infor {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 12rpx;
|
||||
width: 284rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .text .money-section {
|
||||
margin-top: 18rpx;
|
||||
font-size: 22rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .text .money {
|
||||
font-size: 36rpx;
|
||||
color: #333;
|
||||
font-family: 'Regular';
|
||||
color: #FF7D00;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .carnum {
|
||||
height: 36rpx;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .carnum view {
|
||||
width: 66rpx;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
line-height: 36rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .carnum .reduce {
|
||||
border-right: 0;
|
||||
border-radius: 3rpx 0 0 3rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .carnum .reduce.on {
|
||||
border-color: #e3e3e3;
|
||||
color: #dedede;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .carnum .plus {
|
||||
border-left: 0;
|
||||
border-radius: 0 3rpx 3rpx 0;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .carnum .plus.on {
|
||||
border-color: #e3e3e3;
|
||||
color: #dedede;
|
||||
}
|
||||
|
||||
.splitOrder .items .picTxt .carnum .num {
|
||||
color: #282828;
|
||||
background: #F5F5F5;
|
||||
width: 72rpx;
|
||||
}
|
||||
</style>
|
||||
159
components/timeranges/index.vue
Normal file
159
components/timeranges/index.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="time1" :class='isShow==true?"on":""'>
|
||||
<view class="top acea-row row-between-wrapper">
|
||||
<text @tap="cancel">取消</text>
|
||||
<text @tap="confirm">确定</text>
|
||||
</view>
|
||||
<picker-view class="picker" :value="value" @change="getime" indicator-style="height:34px;">
|
||||
<picker-view-column>
|
||||
<view class="hours" v-for="(item,index) in hoursList" :key="index">{{item}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="minutes" v-for="(item,index) in minutes" :key="index">{{item}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="center">-</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="hours" v-for="(item,index) in hoursList" :key="index">{{item}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="minutes" v-for="(item,index) in minutes" :key="index">{{item}}</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
<view class="mask" @tap="cancel" catchtouchmove="true" :hidden="isShow==false"></view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
let minutes=[]
|
||||
for (let i = 0; i <= 59; i++) {
|
||||
if(i<10){
|
||||
i="0"+i
|
||||
}
|
||||
minutes.push(i)
|
||||
}
|
||||
let hoursList = []
|
||||
for (let i = 0; i <= 23; i++) {
|
||||
if(i<10){
|
||||
i="0"+i
|
||||
}
|
||||
hoursList.push(i)
|
||||
}
|
||||
export default{
|
||||
props:{
|
||||
isShow:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
time:{
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
time:function(){
|
||||
this.value=this.time
|
||||
}
|
||||
},
|
||||
created(){
|
||||
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
value:this.time,//默认结束开始时间
|
||||
hoursList,
|
||||
minutes,
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
confirm(){
|
||||
let time = this.value[0]+":"+this.value[1]+" - "+this.value[3]+":"+this.value[4]
|
||||
if(this.value[3]>this.value[0] || (this.value[3]==this.value[0] && this.value[4]>=this.value[1])){
|
||||
this.$emit("confrim",{time:time,val:this.value})
|
||||
}else{
|
||||
return this.$util.Tips({
|
||||
title: '开始时间必须小于结束时间'
|
||||
});
|
||||
}
|
||||
},
|
||||
cancel(){
|
||||
let time = this.value[0]+":"+this.value[1]+" - "+this.value[3]+":"+this.value[4]
|
||||
this.$emit("cancel",{time:time})
|
||||
},
|
||||
getime(e){
|
||||
let val = e.detail.value
|
||||
this.value[0] = this.hoursList[val[0]]
|
||||
this.value[1] = this.minutes[val[1]]
|
||||
this.value[2] = val[2]
|
||||
this.value[3] = this.hoursList[val[3]]
|
||||
this.value[4] = this.minutes[val[4]]
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.time1{
|
||||
width:100%;
|
||||
margin: 0 auto;
|
||||
background-color:#FFFFFF;
|
||||
color: #000;
|
||||
height: 568rpx;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
transform: translate3d(0, 200%, 0);
|
||||
transition: all .3s cubic-bezier(.25, .5, .5, .9);
|
||||
&.on{
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
.top{
|
||||
height: 90rpx;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 0 30rpx;
|
||||
text{
|
||||
font-size: 32rpx;
|
||||
&:nth-child(1){
|
||||
color: #888;
|
||||
}
|
||||
&:nth-child(2){
|
||||
color: #007aff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tip12{
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
view{
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
line-height: 100rpx;
|
||||
font-size: 40rpx;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
.hours{
|
||||
font-size: 32rpx;
|
||||
color: #000;
|
||||
line-height:34px;
|
||||
text-align: center;
|
||||
}
|
||||
.minutes{
|
||||
font-size: 32rpx;
|
||||
color: #000;
|
||||
line-height:34px;
|
||||
text-align: center;
|
||||
}
|
||||
.center{
|
||||
line-height:34px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.picker{
|
||||
width: 100%;
|
||||
height: 476rpx;
|
||||
}
|
||||
</style>
|
||||
144
components/tui-drawer/tui-drawer.vue
Normal file
144
components/tui-drawer/tui-drawer.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-if="mask" class="tui-drawer-mask" :class="{ 'tui-drawer-mask_show': visible }" :style="{ zIndex: maskZIndex }"
|
||||
@tap="handleMaskClick" @touchmove.stop.prevent="moveHandle"></view>
|
||||
<view
|
||||
class="tui-drawer-container"
|
||||
:class="[`tui-drawer-container_${mode}`, visible ? `tui-drawer-${mode}__show` : '']"
|
||||
:style="{ zIndex: zIndex, backgroundColor: backgroundColor }"
|
||||
@touchmove.stop.prevent="moveHandle"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 超过一屏时插槽使用scroll-view
|
||||
**/
|
||||
export default {
|
||||
name: 'tuiDrawer',
|
||||
emits: ['close'],
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// left right bottom top
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
//drawer z-index
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 990
|
||||
},
|
||||
//mask z-index
|
||||
maskZIndex: {
|
||||
type: [Number, String],
|
||||
default: 980
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moveHandle(){
|
||||
return false
|
||||
},
|
||||
handleMaskClick() {
|
||||
if (!this.maskClosable) {
|
||||
return;
|
||||
}
|
||||
this.$emit('close', {});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-drawer-mask {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
.tui-drawer-mask_show {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-drawer-container {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
height: 100.2%;
|
||||
top: 0;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
transform-origin: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
overflow-y: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-ms-touch-action: pan-y cross-slide-y;
|
||||
-ms-scroll-chaining: none;
|
||||
-ms-scroll-limit: 0 50 0 50;
|
||||
}
|
||||
.tui-drawer-container_left {
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translate3d(-100%, -50%, 0);
|
||||
}
|
||||
|
||||
.tui-drawer-container_right {
|
||||
right: 0;
|
||||
top: 50%;
|
||||
left: auto;
|
||||
transform: translate3d(100%, -50%, 0);
|
||||
}
|
||||
|
||||
.tui-drawer-container_bottom,
|
||||
.tui-drawer-container_top {
|
||||
width: 100%;
|
||||
height: auto !important;
|
||||
min-height: 20rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform-origin: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
.tui-drawer-container_bottom {
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
.tui-drawer-container_top {
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
.tui-drawer-left__show,
|
||||
.tui-drawer-right__show {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, -50%, 0);
|
||||
}
|
||||
.tui-drawer-top__show,
|
||||
.tui-drawer-bottom__show {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
</style>
|
||||
209
components/tui-modal/index.vue
Normal file
209
components/tui-modal/index.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<view class="tui-modal__container" :class="[show ? 'tui-modal-show' : '']" :style="{zIndex:zIndex}">
|
||||
<view class="tui-modal-box"
|
||||
:class="[fadeIn || show ? 'tui-modal-normal' : 'tui-modal-scale', show ? 'tui-modal-show' : '']">
|
||||
<view v-if="!custom">
|
||||
<view class="tui-modal-title" v-if="title">{{ title }}</view>
|
||||
<view class="tui-modal-content" :class="[title ? '' : 'tui-mtop']"
|
||||
>{{ content }}</view>
|
||||
<view class="tui-modalBtn-box">
|
||||
<view class="tui-modal-btn flex-center tui-modal-btn-cancel" @tap="handleClick(0)">{{cancelText}}</view>
|
||||
<view class="tui-modal-btn flex-center tui-modal-btn-confirm" @tap="handleClick(1)">{{confirmText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isMask" class="tui-modal-mask" :class="[show ? 'tui-mask-show' : '']"
|
||||
:style="{zIndex:maskZIndex,background:maskColor}" @tap="handleClickCancel"
|
||||
@touchmove.stop.prevent></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiModal',
|
||||
emits: ['click', 'cancel'],
|
||||
props: {
|
||||
//是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//内容
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
cancelText:{
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
confirmText:{
|
||||
type: String,
|
||||
default: '确定'
|
||||
},
|
||||
//点击遮罩 是否可关闭
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否显示mask
|
||||
isMask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maskColor: {
|
||||
type: String,
|
||||
default: 'rgba(0, 0, 0, 0.6)'
|
||||
},
|
||||
//淡入效果,自定义弹框插入input输入框时传true
|
||||
fadeIn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//自定义弹窗内容
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//容器z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 9997
|
||||
},
|
||||
//mask z-index
|
||||
maskZIndex: {
|
||||
type: Number,
|
||||
default: 9990
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
handleClick(index) {
|
||||
if (!this.show) return;
|
||||
this.$emit('click', {
|
||||
index: Number(index)
|
||||
});
|
||||
},
|
||||
handleClickCancel() {
|
||||
if (!this.maskClosable) return;
|
||||
this.$emit('cancel');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-modal__container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-modal-box {
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease-in-out;
|
||||
width:600rpx;
|
||||
padding: 40rpx;
|
||||
border-radius:32rpx;
|
||||
background-color: #fff;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.tui-modal-scale {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.tui-modal-normal {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.tui-modal-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-modal-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-mask-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-modal-title {
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
line-height: 52rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tui-modal-content {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 30rpx;
|
||||
line-height: 40rpx;
|
||||
margin: 24rpx auto 40rpx;
|
||||
}
|
||||
|
||||
.tui-mtop {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.tui-mbtm {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.tui-modalBtn-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tui-flex-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tui-modal-btn {
|
||||
width: 244rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.tui-modal-btn-cancel{
|
||||
border: 1px solid var(--view-theme);
|
||||
color: var(--view-theme);
|
||||
}
|
||||
.tui-modal-btn-confirm{
|
||||
background-color: var(--view-theme);
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
315
components/tui-swipe-action/index.vue
Normal file
315
components/tui-swipe-action/index.vue
Normal file
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<view class="tui-swipeout-wrap" :style="{ backgroundColor: backgroundColor }">
|
||||
<view class="tui-swipeout-item" :class="[isShowBtn ? 'swipe-action-show' : '']"
|
||||
:style="{ transform: 'translate(' + position.pageX + 'px,0)' }">
|
||||
<view class="tui-swipeout-content" @touchstart="handlerTouchstart"
|
||||
@touchmove="handlerTouchmove" @touchend="handlerTouchend" @mousedown="handlerTouchstart"
|
||||
@mousemove="handlerTouchmove" @mouseup="handlerTouchend">
|
||||
<slot name="content"></slot>
|
||||
</view>
|
||||
<view class="tui-swipeout-button-right-group" v-if="actions.length > 0" @touchend.stop="loop">
|
||||
<view class="tui-swipeout-button-right-item" v-for="(item, index) in actions" :key="index"
|
||||
:style="{ backgroundColor: item.background || '#f7f7f7', color: item.color, width: item.width + 'px' }"
|
||||
:data-index="index" @tap="handlerButton">
|
||||
<image :src="item.icon" v-if="item.icon"
|
||||
:style="{ width: px(item.imgWidth), height: px(item.imgHeight) }"></image>
|
||||
<text :style="{ fontSize: px(item.fontsize) }">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!--actions长度设置为0,可直接传按钮进来-->
|
||||
<view class="tui-swipeout-button-right-group" @touchend.stop="loop" @tap="handlerParentButton"
|
||||
v-if="actions.length === 0" :style="{ width: operateWidth + 'px', right: '-' + operateWidth + 'px' }">
|
||||
<slot name="button"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isShowBtn && showMask" class="swipe-action_mask" @tap.stop="closeButtonGroup"
|
||||
@touchstart.stop.prevent="closeButtonGroup"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiSwipeAction',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
// name: '删除',
|
||||
// color: '#fff',
|
||||
// fontsize: 32,//单位rpx
|
||||
// width: 80, //单位px
|
||||
// icon: 'like.png',//此处为图片地址
|
||||
// background: '#ed3f14'
|
||||
actions: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
//点击按钮时是否自动关闭
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//设为false,可以滑动多行不关闭菜单
|
||||
showMask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
operateWidth: {
|
||||
type: Number,
|
||||
default: 80
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
//禁止滑动
|
||||
forbid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//手动开关
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//背景色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
actions(newValue, oldValue) {
|
||||
this.updateButtonSize();
|
||||
},
|
||||
open(newValue) {
|
||||
this.manualSwitch(newValue);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
//start position
|
||||
tStart: {
|
||||
pageX: 0,
|
||||
pageY: 0
|
||||
},
|
||||
//限制滑动距离
|
||||
limitMove: 0,
|
||||
//move position
|
||||
position: {
|
||||
pageX: 0,
|
||||
pageY: 0
|
||||
},
|
||||
isShowBtn: false,
|
||||
move: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.updateButtonSize();
|
||||
},
|
||||
methods: {
|
||||
swipeDirection(x1, x2, y1, y2) {
|
||||
return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : y1 - y2 > 0 ? 'Up' :
|
||||
'Down';
|
||||
},
|
||||
//阻止事件冒泡
|
||||
loop() {
|
||||
},
|
||||
updateButtonSize() {
|
||||
const actions = this.actions;
|
||||
if (actions.length > 0) {
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
let limitMovePosition = 0;
|
||||
actions.forEach(item => {
|
||||
limitMovePosition += item.width || 0;
|
||||
});
|
||||
this.limitMove = limitMovePosition;
|
||||
} else {
|
||||
this.limitMove = this.operateWidth;
|
||||
}
|
||||
},
|
||||
handlerTouchstart(event) {
|
||||
if (this.forbid) return;
|
||||
let touches = event.touches
|
||||
if (touches && touches.length > 1) return;
|
||||
this.move = true;
|
||||
touches = touches ? event.touches[0] : {};
|
||||
if (!touches || (touches.pageX === undefined && touches.pageY === undefined)) {
|
||||
touches = {
|
||||
pageX: event.pageX,
|
||||
pageY: event.pageY
|
||||
};
|
||||
}
|
||||
const tStart = this.tStart;
|
||||
if (touches) {
|
||||
for (let i in tStart) {
|
||||
if (touches[i]) {
|
||||
tStart[i] = touches[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
swipper(touches) {
|
||||
const start = this.tStart;
|
||||
const spacing = {
|
||||
pageX: touches.pageX - start.pageX,
|
||||
pageY: touches.pageY - start.pageY
|
||||
};
|
||||
if (this.limitMove < Math.abs(spacing.pageX)) {
|
||||
spacing.pageX = -this.limitMove;
|
||||
}
|
||||
this.position = spacing;
|
||||
},
|
||||
handlerTouchmove(event) {
|
||||
if (this.forbid || !this.move) return;
|
||||
const start = this.tStart;
|
||||
let touches = event.touches ? event.touches[0] : {};
|
||||
if (!touches || (touches.pageX === undefined && touches.pageY === undefined)) {
|
||||
touches = {
|
||||
pageX: event.pageX,
|
||||
pageY: event.pageY
|
||||
};
|
||||
}
|
||||
if (touches) {
|
||||
const direction = this.swipeDirection(start.pageX, touches.pageX, start.pageY, touches.pageY);
|
||||
if (direction === 'Left' && Math.abs(this.position.pageX) !== this.limitMove) {
|
||||
this.swipper(touches);
|
||||
}
|
||||
}
|
||||
},
|
||||
handlerTouchend(event) {
|
||||
if (this.forbid || !this.move) return;
|
||||
this.move = false;
|
||||
const start = this.tStart;
|
||||
let touches = event.changedTouches ? event.changedTouches[0] : {};
|
||||
if (!touches || (touches.pageX === undefined && touches.pageY === undefined)) {
|
||||
touches = {
|
||||
pageX: event.pageX,
|
||||
pageY: event.pageY
|
||||
};
|
||||
}
|
||||
if (touches) {
|
||||
const direction = this.swipeDirection(start.pageX, touches.pageX, start.pageY, touches.pageY);
|
||||
const spacing = {
|
||||
pageX: touches.pageX - start.pageX,
|
||||
pageY: touches.pageY - start.pageY
|
||||
};
|
||||
if (Math.abs(spacing.pageX) >= 40 && direction === 'Left') {
|
||||
spacing.pageX = spacing.pageX < 0 ? -this.limitMove : this.limitMove;
|
||||
this.isShowBtn = true;
|
||||
} else {
|
||||
spacing.pageX = 0;
|
||||
}
|
||||
if (spacing.pageX== 0) {
|
||||
this.isShowBtn = false;
|
||||
}
|
||||
this.position = spacing;
|
||||
|
||||
}
|
||||
},
|
||||
handlerButton(event) {
|
||||
if (this.closable) {
|
||||
this.closeButtonGroup();
|
||||
}
|
||||
const dataset = event.currentTarget.dataset;
|
||||
this.$emit('click', {
|
||||
index: Number(dataset.index),
|
||||
item: this.params
|
||||
});
|
||||
},
|
||||
closeButtonGroup() {
|
||||
this.position = {
|
||||
pageX: 0,
|
||||
pageY: 0
|
||||
};
|
||||
this.isShowBtn = false;
|
||||
},
|
||||
//控制自定义按钮菜单
|
||||
handlerParentButton(event) {
|
||||
if (this.closable) {
|
||||
this.closeButtonGroup();
|
||||
}
|
||||
},
|
||||
manualSwitch(isOpen) {
|
||||
let x = 0;
|
||||
if (isOpen) {
|
||||
if (this.actions.length === 0) {
|
||||
x = this.operateWidth;
|
||||
} else {
|
||||
let width = 0;
|
||||
this.actions.forEach(item => {
|
||||
width += item.width;
|
||||
});
|
||||
x = width;
|
||||
}
|
||||
}
|
||||
this.position = {
|
||||
pageX: -x,
|
||||
pageY: 0
|
||||
};
|
||||
},
|
||||
px(num) {
|
||||
return uni.upx2px(num) + 'px';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-swipeout-wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.swipe-action-show {
|
||||
position: relative;
|
||||
z-index: 998;
|
||||
}
|
||||
|
||||
.tui-swipeout-item {
|
||||
width: 100%;
|
||||
/* padding: 15px 20px; */
|
||||
box-sizing: border-box;
|
||||
transition: transform 0.2s ease;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tui-swipeout-content {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-swipeout-button-right-group {
|
||||
position: absolute;
|
||||
right: -100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tui-swipeout-button-right-item {
|
||||
height: 100%;
|
||||
float: left;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.swipe-action_mask {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
z-index: 997;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
397
components/uniNoticeBar/uni-notice-bar.vue
Normal file
397
components/uniNoticeBar/uni-notice-bar.vue
Normal file
@@ -0,0 +1,397 @@
|
||||
<template>
|
||||
<view v-if="show" class="uni-noticebar" :style="'background-color:'+backgroundColor+';width:'+(100% - prConfig*2)*2+'rpx;'" @click="onClick">
|
||||
<!-- <uni-icons v-if="showIcon === true || showIcon === 'true'" class="uni-noticebar-icon" type="sound"
|
||||
:color="color" size="22" /> -->
|
||||
<view ref="textBox" class="uni-noticebar__content-wrapper"
|
||||
:class="{'uni-noticebar__content-wrapper--scrollable':scrollable, 'uni-noticebar__content-wrapper--single':!scrollable && (single || moreText)}">
|
||||
<view :id="elIdBox" class="uni-noticebar__content"
|
||||
:class="{'uni-noticebar__content--scrollable':scrollable, 'uni-noticebar__content--single':!scrollable && (single || moreText)}">
|
||||
<text :id="elId" ref="animationEle" class="uni-noticebar__content-text"
|
||||
:class="{'uni-noticebar__content-text--scrollable':scrollable,'uni-noticebar__content-text--single':!scrollable && (single || showGetMore)}"
|
||||
:style="{color:color, width:wrapWidth+'px', 'animationDuration': animationDuration, '-webkit-animationDuration': animationDuration ,animationPlayState: webviewHide?'paused':animationPlayState,'-webkit-animationPlayState':webviewHide?'paused':animationPlayState, animationDelay: animationDelay, '-webkit-animationDelay':animationDelay}">{{text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="showGetMore === true || showGetMore === 'true'" class="uni-noticebar__more uni-cursor-point"
|
||||
@click="clickMore">
|
||||
<text v-if="moreText.length > 0" :style="{ color: moreColor }" class="uni-noticebar__more-text">{{ moreText }}</text>
|
||||
<!-- <uni-icons v-else type="right" :color="moreColor" size="16" /> -->
|
||||
</view>
|
||||
<view class="uni-noticebar-close uni-cursor-point" v-if="(showClose === true || showClose === 'true') && (showGetMore === false || showGetMore === 'false')">
|
||||
<!-- <uni-icons
|
||||
type="closeempty" :color="color" size="16" @click="close" /> -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
const dom = weex.requireModule('dom');
|
||||
const animation = weex.requireModule('animation');
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* NoticeBar 自定义导航栏
|
||||
* @description 通告栏组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=30
|
||||
* @property {Number} speed 文字滚动的速度,默认100px/秒
|
||||
* @property {String} text 显示文字
|
||||
* @property {String} backgroundColor 背景颜色
|
||||
* @property {String} color 文字颜色
|
||||
* @property {String} moreColor 查看更多文字的颜色
|
||||
* @property {String} moreText 设置“查看更多”的文本
|
||||
* @property {Boolean} single = [true|false] 是否单行
|
||||
* @property {Boolean} scrollable = [true|false] 是否滚动,为true时,NoticeBar为单行
|
||||
* @property {Boolean} showIcon = [true|false] 是否显示左侧喇叭图标
|
||||
* @property {Boolean} showClose = [true|false] 是否显示左侧关闭按钮
|
||||
* @property {Boolean} showGetMore = [true|false] 是否显示右侧查看更多图标,为true时,NoticeBar为单行
|
||||
* @event {Function} click 点击 NoticeBar 触发事件
|
||||
* @event {Function} close 关闭 NoticeBar 触发事件
|
||||
* @event {Function} getmore 点击”查看更多“时触发事件
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniNoticeBar',
|
||||
emits: ['click', 'getmore', 'close'],
|
||||
props: {
|
||||
prConfig: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
moreText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#FFF9EA'
|
||||
},
|
||||
speed: {
|
||||
// 默认1s滚动100px
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#FF9A43'
|
||||
},
|
||||
moreColor: {
|
||||
type: String,
|
||||
default: '#FF9A43'
|
||||
},
|
||||
single: {
|
||||
// 是否单行
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
scrollable: {
|
||||
// 是否滚动,添加后控制单行效果取消
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
showIcon: {
|
||||
// 是否显示左侧icon
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
showGetMore: {
|
||||
// 是否显示右侧查看更多
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
showClose: {
|
||||
// 是否显示左侧关闭按钮
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
const elIdBox = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
return {
|
||||
textWidth: 0,
|
||||
boxWidth: 0,
|
||||
wrapWidth: '',
|
||||
webviewHide: false,
|
||||
// #ifdef APP-NVUE
|
||||
stopAnimation: false,
|
||||
// #endif
|
||||
elId: elId,
|
||||
elIdBox: elIdBox,
|
||||
show: true,
|
||||
animationDuration: 'none',
|
||||
animationPlayState: 'paused',
|
||||
animationDelay: '0s'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef APP-PLUS
|
||||
var pages = getCurrentPages();
|
||||
var page = pages[pages.length - 1];
|
||||
var currentWebview = page.$getAppWebview();
|
||||
currentWebview.addEventListener('hide', () => {
|
||||
this.webviewHide = true
|
||||
})
|
||||
currentWebview.addEventListener('show', () => {
|
||||
this.webviewHide = false
|
||||
})
|
||||
// #endif
|
||||
this.$nextTick(() => {
|
||||
this.initSize()
|
||||
})
|
||||
},
|
||||
// #ifdef APP-NVUE
|
||||
beforeDestroy() {
|
||||
this.stopAnimation = true
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
initSize() {
|
||||
if (this.scrollable) {
|
||||
// #ifndef APP-NVUE
|
||||
let query = [],
|
||||
boxWidth = 0,
|
||||
textWidth = 0;
|
||||
let textQuery = new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this)
|
||||
// #endif
|
||||
.select(`#${this.elId}`)
|
||||
.boundingClientRect()
|
||||
.exec(ret => {
|
||||
this.textWidth = ret[0].width
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
let boxQuery = new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this)
|
||||
// #endif
|
||||
.select(`#${this.elIdBox}`)
|
||||
.boundingClientRect()
|
||||
.exec(ret => {
|
||||
this.boxWidth = ret[0].width
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
query.push(textQuery)
|
||||
query.push(boxQuery)
|
||||
Promise.all(query).then(() => {
|
||||
this.animationDuration = `${this.textWidth / this.speed}s`
|
||||
this.animationDelay = `-${this.boxWidth / this.speed}s`
|
||||
setTimeout(() => {
|
||||
this.animationPlayState = 'running'
|
||||
}, 1000)
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
dom.getComponentRect(this.$refs['animationEle'], (res) => {
|
||||
let winWidth = uni.getWindowInfo().windowWidth
|
||||
this.textWidth = res.size.width
|
||||
animation.transition(this.$refs['animationEle'], {
|
||||
styles: {
|
||||
transform: `translateX(-${winWidth}px)`
|
||||
},
|
||||
duration: 0,
|
||||
timingFunction: 'linear',
|
||||
delay: 0
|
||||
}, () => {
|
||||
if (!this.stopAnimation) {
|
||||
animation.transition(this.$refs['animationEle'], {
|
||||
styles: {
|
||||
transform: `translateX(-${this.textWidth}px)`
|
||||
},
|
||||
timingFunction: 'linear',
|
||||
duration: (this.textWidth - winWidth) / this.speed * 1000,
|
||||
delay: 1000
|
||||
}, () => {
|
||||
if (!this.stopAnimation) {
|
||||
this.loopAnimation()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
// #ifdef APP-NVUE
|
||||
if (!this.scrollable && (this.single || this.moreText)) {
|
||||
dom.getComponentRect(this.$refs['textBox'], (res) => {
|
||||
this.wrapWidth = res.size.width
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
loopAnimation() {
|
||||
// #ifdef APP-NVUE
|
||||
animation.transition(this.$refs['animationEle'], {
|
||||
styles: {
|
||||
transform: `translateX(0px)`
|
||||
},
|
||||
duration: 0
|
||||
}, () => {
|
||||
if (!this.stopAnimation) {
|
||||
animation.transition(this.$refs['animationEle'], {
|
||||
styles: {
|
||||
transform: `translateX(-${this.textWidth}px)`
|
||||
},
|
||||
duration: this.textWidth / this.speed * 1000,
|
||||
timingFunction: 'linear',
|
||||
delay: 0
|
||||
}, () => {
|
||||
if (!this.stopAnimation) {
|
||||
this.loopAnimation()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
clickMore() {
|
||||
this.$emit('getmore')
|
||||
},
|
||||
close() {
|
||||
this.show = false;
|
||||
this.$emit('close')
|
||||
},
|
||||
onClick() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.uni-noticebar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-cursor-point {
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar-close {
|
||||
margin-left: 8px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.uni-noticebar-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.uni-noticebar__content-wrapper {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-noticebar__content-wrapper--single {
|
||||
/* #ifndef APP-NVUE */
|
||||
line-height: 18px;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar__content-wrapper--single,
|
||||
.uni-noticebar__content-wrapper--scrollable {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-noticebar__content-wrapper--scrollable {
|
||||
position: relative;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
.uni-noticebar__content--scrollable {
|
||||
/* #ifdef APP-NVUE */
|
||||
flex: 0;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
flex: 1;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar__content--single {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex: none;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar__content-text {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
/* #ifndef APP-NVUE */
|
||||
word-break: break-all;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar__content-text--single {
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.uni-noticebar__content-text--scrollable {
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
padding-left: 750rpx;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
padding-left: 100%;
|
||||
animation: notice 10s 0s linear infinite both;
|
||||
animation-play-state: paused;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar__more {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.uni-noticebar__more-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@keyframes notice {
|
||||
100% {
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
427
components/update/app-update.vue
Normal file
427
components/update/app-update.vue
Normal file
@@ -0,0 +1,427 @@
|
||||
<template>
|
||||
<view class="wrap" v-if="popup_show">
|
||||
<view class="popup-bg" :style="getHeight">
|
||||
<view class="popup-content" :class="{ 'popup-content-show': popup_show }">
|
||||
<view class="update-wrap">
|
||||
<image src="./images/img.png" class="top-img"></image>
|
||||
<view class="content">
|
||||
<text class="title">发现新版本{{ update_info.version }}</text>
|
||||
<!-- 升级描述 -->
|
||||
<view class="title-sub" v-html="update_info.info"></view>
|
||||
<!-- 升级按钮 -->
|
||||
<button class="btn" v-if="downstatus < 1" @click="nowUpdate()">
|
||||
立即升级
|
||||
</button>
|
||||
<!-- 下载进度 -->
|
||||
<view class="sche-wrap" v-else>
|
||||
<!-- 更新包下载中 -->
|
||||
<view class="sche-bg">
|
||||
<view class="sche-bg-jindu" :style="lengthWidth"></view>
|
||||
</view>
|
||||
<text class="down-text">下载进度:{{ (downSize / 1024 / 1024).toFixed(2) }}M/{{
|
||||
(fileSize / 1024 / 1024).toFixed(2)
|
||||
}}M</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<image src="./images/close.png" class="close-ioc" @click="closeUpdate()"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let vm;
|
||||
import {
|
||||
getUpdateInfo
|
||||
} from '@/api/public.js'
|
||||
|
||||
export default {
|
||||
name: "appUpdate",
|
||||
//@是否强制更新
|
||||
props: {
|
||||
tabbar: {
|
||||
type: Boolean,
|
||||
default: false, //是否有原生tabbar组件
|
||||
},
|
||||
getVer: {
|
||||
type: Boolean,
|
||||
default: false, //是否有原生tabbar组件
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
popup_show: false, //弹窗是否显示
|
||||
platform: "", //ios or android
|
||||
version: "1.0.0", //当前软件版本
|
||||
need_update: false, // 是否更新
|
||||
downing: false, //是否下载中
|
||||
downstatus: 0, //0未下载 1已开始 2已连接到资源 3已接收到数据 4下载完成
|
||||
update_info: {
|
||||
os: "", //设备系统
|
||||
version: "", //最新版本
|
||||
info: "", //升级说明
|
||||
},
|
||||
fileSize: 0, //文件大小
|
||||
downSize: 0, //已下载大小
|
||||
viewObj: null, //原生遮罩view
|
||||
};
|
||||
},
|
||||
created() {
|
||||
vm = this;
|
||||
if (!this.getVer) this.update()
|
||||
},
|
||||
computed: {
|
||||
// 下载进度计算
|
||||
lengthWidth: function() {
|
||||
let w = (this.downSize / this.fileSize) * 100;
|
||||
if (!w) {
|
||||
w = 0;
|
||||
} else {
|
||||
w = w.toFixed(2);
|
||||
}
|
||||
return {
|
||||
width: w + "%", //return 宽度半分比
|
||||
};
|
||||
},
|
||||
getHeight() {
|
||||
let bottom = 0;
|
||||
if (this.tabbar) {
|
||||
bottom = 50;
|
||||
}
|
||||
return {
|
||||
bottom: bottom + "px",
|
||||
height: "auto",
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 检查更新
|
||||
update() {
|
||||
// #ifdef APP-PLUS
|
||||
// 获取手机系统信息
|
||||
uni.getSystemInfo({
|
||||
success: function(res) {
|
||||
vm.platform = res.platform; //ios or android
|
||||
console.log("手机系统信息", vm.platform);
|
||||
},
|
||||
});
|
||||
|
||||
// 获取版本号
|
||||
plus.runtime.getProperty(plus.runtime.appid, function(inf) {
|
||||
vm.version = inf.version;
|
||||
});
|
||||
console.log("当前版本", vm.version);
|
||||
this.getUpdateInfo(); //获取更新信息
|
||||
// #endif
|
||||
},
|
||||
|
||||
// 获取线上版本信息
|
||||
getUpdateInfo() {
|
||||
//向后台发起请求,获取最新版本号
|
||||
getUpdateInfo(this.platform === "ios" ? 2 : 1)
|
||||
.then((res) => {
|
||||
if(Array.isArray(res.data)){
|
||||
return this.$emit('isNew')
|
||||
}
|
||||
const tagDate = uni.getStorageSync('app_update_time') || '',
|
||||
nowDate = new Date().toLocaleDateString();
|
||||
if (tagDate !== nowDate && !this.getVer) {
|
||||
uni.setStorageSync('app_update_time', new Date().toLocaleDateString());
|
||||
} else if ((tagDate !== nowDate) && this.getVer) {
|
||||
if (!res.data.is_force) return
|
||||
} else if (tagDate == nowDate && !this.getVer && !res.data.is_force) {
|
||||
return
|
||||
}
|
||||
// 这里的返回的数据跟后台约定
|
||||
let data = res.data;
|
||||
// 循环获取当前设备对应的更新数据
|
||||
vm.update_info = data;
|
||||
if (!vm.update_info.platform) {
|
||||
// 后台未配置当前系统的升级数据
|
||||
} else {
|
||||
vm.checkUpdate(); ///检查是否更新
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
vm.popup_show = false
|
||||
});
|
||||
},
|
||||
// 检查是否更新
|
||||
checkUpdate() {
|
||||
vm.need_update = vm.compareVersion(vm.version, vm.update_info.version); // 检查是否需要升级
|
||||
if (vm.need_update) {
|
||||
vm.popup_show = true; //线上版本号大于当前安装的版本号 显示升级框
|
||||
if (vm.tabbar) {
|
||||
//页面是否有原生tabbar组件
|
||||
// 创建原生view用来遮罩tabbar的点击事件 (如果是没有用原生的tabbar这一步可以取消)
|
||||
vm.viewObj = new plus.nativeObj.View("viewObj", {
|
||||
bottom: "0px",
|
||||
left: "0px",
|
||||
height: "50px",
|
||||
width: "100%",
|
||||
backgroundColor: "rgba(0,0,0,.6)",
|
||||
});
|
||||
vm.viewObj.show(); //显示原生遮罩
|
||||
}
|
||||
} else {
|
||||
this.$emit('isNew')
|
||||
}
|
||||
},
|
||||
|
||||
// 取消更新
|
||||
closeUpdate() {
|
||||
if (vm.update_info.is_force) {
|
||||
// 强制更新,取消退出app
|
||||
this.platform == "android" ?
|
||||
plus.runtime.quit() :
|
||||
plus.ios
|
||||
.import("UIApplication")
|
||||
.sharedApplication()
|
||||
.performSelector("exit");
|
||||
} else {
|
||||
vm.popup_show = false; //关闭升级弹窗
|
||||
if (vm.viewObj) vm.viewObj.hide(); //隐藏原生遮罩
|
||||
}
|
||||
},
|
||||
// 立即更新
|
||||
nowUpdate() {
|
||||
if (vm.downing) return false; //如果正在下载就停止操作
|
||||
vm.downing = true; //状态改变 正在下载中
|
||||
|
||||
if (/\.apk$/.test(vm.update_info.url)) {
|
||||
// 如果是apk地址
|
||||
vm.download_wgt(); // 安装包/升级包更新
|
||||
} else if (/\.wgt$/.test(vm.update_info.url)) {
|
||||
// 如果是更新包
|
||||
vm.download_wgt(); // 安装包/升级包更新
|
||||
} else {
|
||||
plus.runtime.openURL(vm.update_info.url, function() {
|
||||
//调用外部浏览器打开更新地址
|
||||
plus.nativeUI.toast("打开错误");
|
||||
});
|
||||
}
|
||||
},
|
||||
// 下载升级资源包
|
||||
download_wgt() {
|
||||
plus.nativeUI.showWaiting("下载更新文件..."); //下载更新文件...
|
||||
let options = {
|
||||
method: "get",
|
||||
};
|
||||
let dtask = plus.downloader.createDownload(
|
||||
vm.update_info.url,
|
||||
options,
|
||||
function(d, status) {}
|
||||
);
|
||||
|
||||
dtask.addEventListener("statechanged", function(task, status) {
|
||||
if (status === null) {} else if (status == 200) {
|
||||
//在这里打印会不停的执行,请注意,正式上线切记不要在这里打印东西///////////////////////////////////////////////////
|
||||
vm.downstatus = task.state;
|
||||
switch (task.state) {
|
||||
case 3: // 已接收到数据
|
||||
vm.downSize = task.downloadedSize;
|
||||
if (task.totalSize) {
|
||||
vm.fileSize = task.totalSize; //服务器须返回正确的content-length才会有长度
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
vm.installWgt(task.filename); // 安装wgt包
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
plus.nativeUI.closeWaiting();
|
||||
plus.nativeUI.toast("下载出错");
|
||||
vm.downing = false;
|
||||
vm.downstatus = 0;
|
||||
}
|
||||
});
|
||||
dtask.start();
|
||||
},
|
||||
|
||||
// 安装文件
|
||||
installWgt(path) {
|
||||
plus.nativeUI.showWaiting("安装更新文件..."); //安装更新文件...
|
||||
plus.runtime.install(
|
||||
path, {},
|
||||
function() {
|
||||
plus.nativeUI.closeWaiting();
|
||||
// 应用资源下载完成!
|
||||
plus.nativeUI.alert("应用资源下载完成!", function() {
|
||||
plus.runtime.restart();
|
||||
});
|
||||
},
|
||||
|
||||
function(e) {
|
||||
plus.nativeUI.closeWaiting();
|
||||
// 安装更新文件失败
|
||||
plus.nativeUI.alert("安装更新文件失败[" + e.code + "]:" + e.message);
|
||||
}
|
||||
);
|
||||
},
|
||||
// 对比版本号
|
||||
compareVersion(ov, nv) {
|
||||
if (!ov || !nv || ov == "" || nv == "") {
|
||||
return false;
|
||||
}
|
||||
let b = false,
|
||||
ova = ov.split(".", 4),
|
||||
nva = nv.split(".", 4);
|
||||
for (let i = 0; i < ova.length && i < nva.length; i++) {
|
||||
let so = ova[i],
|
||||
no = parseInt(so),
|
||||
sn = nva[i],
|
||||
nn = parseInt(sn);
|
||||
if (nn > no || sn.length > so.length) {
|
||||
return true;
|
||||
} else if (nn < no) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (nva.length > ova.length && 0 == nv.indexOf(ov)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.popup-bg {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0rpx;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 750rpx;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.popup-content-show {
|
||||
animation: mymove 500ms;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
@keyframes mymove {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
/*开始为原始大小*/
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.update-wrap {
|
||||
width: 580rpx;
|
||||
border-radius: 18rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #ffffff;
|
||||
padding: 170rpx 30rpx 0;
|
||||
|
||||
.top-img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 256rpx;
|
||||
top: -128rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 40rpx;
|
||||
|
||||
.title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #6526f3;
|
||||
}
|
||||
|
||||
.title-sub {
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 460rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 100px;
|
||||
background-color: #6526f3;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close-ioc {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.sche-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 10rpx 50rpx 0;
|
||||
|
||||
.sche-wrap-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.sche-bg {
|
||||
position: relative;
|
||||
background-color: #cccccc;
|
||||
height: 30rpx;
|
||||
border-radius: 100px;
|
||||
width: 480rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.sche-bg-jindu {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 30rpx;
|
||||
min-width: 40rpx;
|
||||
border-radius: 100px;
|
||||
background: url(images/round.png) #5775e7 center right 4rpx no-repeat;
|
||||
background-size: 26rpx 26rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.down-text {
|
||||
font-size: 24rpx;
|
||||
color: #5674e5;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
BIN
components/update/images/close.png
Normal file
BIN
components/update/images/close.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
components/update/images/img.png
Normal file
BIN
components/update/images/img.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
BIN
components/update/images/round.png
Normal file
BIN
components/update/images/round.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
174
components/userEvaluation/index.vue
Normal file
174
components/userEvaluation/index.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<!-- 评价列表 -->
|
||||
<view :style="colorStyle">
|
||||
<scroll-view ref="scroll" scroll-x="true" class="scroll w-690 mt-24 pl-20" show-scrollbar="true" @scroll="scroll">
|
||||
<view id="scrollBox" ref="scrollBox" class="scrll-box">
|
||||
<view class="inline-block mr-20" v-for="item in reply" :key="item.id" @click.stop="details(item)">
|
||||
<view class="h-158 rd-16rpx bg--w111-f5f5f5 flex justify-between">
|
||||
<view class="flex-1 p-24">
|
||||
<view class="flex-y-center">
|
||||
<view class="w-64 h-64 rd-32rpx relative" :class="{'svip-border': item.vip_status}">
|
||||
<image :src="item.avatar" class="w-full h-full rd-32rpx"></image>
|
||||
<image class="vip-badge" src="@/static/img/svip_badge.png" v-show="item.vip_status"></image>
|
||||
</view>
|
||||
<view class="flex-col pl-16">
|
||||
<view class="flex-y-center">
|
||||
<text class="text--w111-333 fs-24">{{ item.nickname }}</text>
|
||||
<view class="vip flex-center" v-if="item.level_name">
|
||||
<text class="iconfont icon-huiyuandengji"></text>
|
||||
V{{item.level_name}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex">
|
||||
<text class="iconfont icon-ic_star1 fs-18 text-primary-con" v-for="star in Number(item.star)" :key="star"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="w-324 mt-12 text--w111-333 fs-24 white-nowrap line1">{{ item.comment }}</view>
|
||||
</view>
|
||||
<image v-if="item.pics.length" class="w-124 h-124 rd-12rpx block mt-16 mr-16" :src="item.pics[0]" mode="aspectFill"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="flex-center mt-24" v-if="reply.length > 1">
|
||||
<view class="w-64 h-6 rd-3px bg--w111-eee scrpll-l">
|
||||
<view class="w-32 h-6 rd-3px scrpll-line" :style="{ left: srollLeft + '%' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import colors from "@/mixins/color";
|
||||
import { getReplyPraise, getUnReplyPraise } from '@/api/store.js';
|
||||
import { toLogin } from '../../libs/login';
|
||||
export default {
|
||||
computed: mapGetters(['isLogin']),
|
||||
props: {
|
||||
reply: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
fromTo: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
srollLeft: 0, // 距离左边
|
||||
srollBoxWidth: 0, //内部宽度
|
||||
boxWidth: 0 //容器宽度
|
||||
};
|
||||
},
|
||||
mixins: [colors],
|
||||
mounted() {
|
||||
this.$nextTick((e) => {
|
||||
this.srollLeft = 0;
|
||||
const query = wx.createSelectorQuery().in(this);
|
||||
query
|
||||
.select('.scrll-box')
|
||||
.boundingClientRect((res) => {
|
||||
this.srollBoxWidth = res.width;
|
||||
})
|
||||
.exec();
|
||||
query
|
||||
.select('.scroll')
|
||||
.boundingClientRect((res) => {
|
||||
this.boxWidth = res.width - 10;
|
||||
})
|
||||
.exec();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
scroll(e) {
|
||||
this.srollLeft = ((e.detail.scrollLeft / (this.srollBoxWidth - this.boxWidth)) * 100).toFixed(0) / 2;
|
||||
},
|
||||
details(item) {
|
||||
if (this.isLogin) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/goods/goods_comment_con/comment_con?id=' + item.id
|
||||
});
|
||||
} else {
|
||||
this.$emit('changeLogin');
|
||||
}
|
||||
},
|
||||
getpreviewImage: function (indexw, indexn) {
|
||||
uni.previewImage({
|
||||
urls: this.reply[indexw].pics,
|
||||
current: this.reply[indexw].pics[indexn]
|
||||
});
|
||||
},
|
||||
praise(item, indexw) {
|
||||
if (this.isLogin) {
|
||||
if (item.is_praise) {
|
||||
getUnReplyPraise(item.id).then((res) => {
|
||||
item.is_praise = !item.is_praise;
|
||||
item.praise = item.praise - 1;
|
||||
this.$emit('replyFun', this.reply);
|
||||
return this.$util.Tips({
|
||||
title: res.msg
|
||||
});
|
||||
});
|
||||
} else {
|
||||
getReplyPraise(item.id).then((res) => {
|
||||
item.is_praise = !item.is_praise;
|
||||
item.praise = item.praise + 1;
|
||||
this.$emit('replyFun', this.reply);
|
||||
return this.$util.Tips({
|
||||
title: res.msg
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toLogin();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.scrll-box {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: max-content;
|
||||
}
|
||||
.scrpll-l {
|
||||
position: relative;
|
||||
}
|
||||
.scrpll-line {
|
||||
position: absolute;
|
||||
background-color: var(--view-theme);
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.text-primary-con {
|
||||
color: var(--view-theme);
|
||||
}
|
||||
.vip{
|
||||
width: 64rpx;
|
||||
height: 26rpx;
|
||||
background: #FEF0D9;
|
||||
border: 1px solid #FACC7D;
|
||||
border-radius: 50rpx;
|
||||
font-size: 18rpx;
|
||||
font-weight: 500;
|
||||
color: #DFA541;
|
||||
margin-left: 10rpx;
|
||||
.iconfont {
|
||||
font-size: 16rpx;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
}
|
||||
.svip-border{
|
||||
border: 2rpx solid #F1BB0D;
|
||||
}
|
||||
.vip-badge{
|
||||
position: absolute;
|
||||
top: -14rpx;
|
||||
right: -8rpx;
|
||||
width: 32rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
</style>
|
||||
160
components/weixinTip/index.vue
Normal file
160
components/weixinTip/index.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="tips-box" :style="[fixedTop]" v-if="showTip">
|
||||
<view class="tips-content" :style="[contentStyle]" @click.stop="hideTip">
|
||||
<view class="span">
|
||||
<text>{{ tips }}</text>
|
||||
<text class="iconfont icon-ic_close2"></text>
|
||||
</view>
|
||||
<view class="arrow" :style="[addStyle]"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
showTip: false,
|
||||
marginRight: '10px',
|
||||
fixedRight: 52
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef MP
|
||||
if (wx.canIUse('checkIsAddedToMyMiniProgram')) {
|
||||
this.checkIsAddedToMyMiniProgram();
|
||||
} else {
|
||||
this.showTip = true;
|
||||
setTimeout(() => {
|
||||
this.hideTip()
|
||||
}, this.duration * 1000);
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
props: {
|
||||
tips: {
|
||||
type: String,
|
||||
default: '点击添加到我的小程序'
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkIsAddedToMyMiniProgram() {
|
||||
try {
|
||||
wx.checkIsAddedToMyMiniProgram({
|
||||
success: (res) => {
|
||||
if (res.added) {
|
||||
this.showTip = false;
|
||||
} else {
|
||||
this.showTip = true;
|
||||
setTimeout(() => {
|
||||
this.hideTip()
|
||||
}, this.duration * 1000);
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
this.showTip = true;
|
||||
setTimeout(() => {
|
||||
this.hideTip()
|
||||
}, this.duration * 1000);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('error: ', error);
|
||||
}
|
||||
},
|
||||
hideTip() {
|
||||
// #ifdef MP
|
||||
this.showTip = false;
|
||||
this.$emit('change');
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
contentStyle() {
|
||||
return {
|
||||
backgroundColor: this.color,
|
||||
marginRight: this.marginRight
|
||||
};
|
||||
},
|
||||
fixedTop() {
|
||||
let res = wx.getMenuButtonBoundingClientRect();
|
||||
let sysHeight = wx.getSystemInfoSync().statusBarHeight;
|
||||
return {
|
||||
top: sysHeight + res.height + 8 + 'px'
|
||||
};
|
||||
},
|
||||
addStyle() {
|
||||
return {
|
||||
'border-color': 'transparent transparent ' + this.color + ' transparent',
|
||||
right: this.fixedRight + 'px'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tips-box {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
z-index: 99999;
|
||||
opacity: 0.8;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
flex-direction: column;
|
||||
width: 600rpx;
|
||||
animation: tips 1s linear infinite;
|
||||
}
|
||||
|
||||
.tips-content {
|
||||
border-width: 0;
|
||||
margin-top: 20rpx;
|
||||
position: relative;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 18rpx 20rpx;
|
||||
}
|
||||
|
||||
.span {
|
||||
color: #333333;
|
||||
font-size: 28rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
.icon-ic_close2{
|
||||
color: #aaa;
|
||||
font-size: 24rpx;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
.arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
top: -38rpx;
|
||||
border-width: 20rpx;
|
||||
border-style: solid;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes tips {
|
||||
0% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
923
components/zb-code/qrcode.js
Normal file
923
components/zb-code/qrcode.js
Normal file
@@ -0,0 +1,923 @@
|
||||
|
||||
let QRCode={};
|
||||
(function(){
|
||||
function unicodeFormat8(code){
|
||||
var c0,c1,c2;
|
||||
if(code<128){
|
||||
return[code];
|
||||
}else if(code<2048){
|
||||
c0=192+(code>>6);
|
||||
c1=128+(code&63);
|
||||
return[c0,c1];
|
||||
}else{
|
||||
c0=224+(code>>12);
|
||||
c1=128+(code>>6&63);
|
||||
c2=128+(code&63);
|
||||
return[c0,c1,c2];
|
||||
}
|
||||
}
|
||||
function getUTF8Bytes(string){
|
||||
var utf8codes=[];
|
||||
for(var i=0;i<string.length;i++){
|
||||
var code=string.charCodeAt(i);
|
||||
var utf8=unicodeFormat8(code);
|
||||
for(var j=0;j<utf8.length;j++){
|
||||
utf8codes.push(utf8[j]);
|
||||
}
|
||||
}
|
||||
return utf8codes;
|
||||
}
|
||||
function QRCodeAlg(data,twodimensionalcodeee5conversionmethod){
|
||||
this.typeNumber=-1;
|
||||
this.errorCorrectLevel=twodimensionalcodeee5conversionmethod;
|
||||
this.modules=null;
|
||||
this.moduleCount=0;
|
||||
this.dataCache=null;
|
||||
this.rsBlocks=null;
|
||||
this.totalDataCount=-1;
|
||||
this.data=data;
|
||||
this.utf8bytes=getUTF8Bytes(data);
|
||||
this.make();
|
||||
}
|
||||
QRCodeAlg.prototype={
|
||||
constructor:QRCodeAlg,
|
||||
getModuleCount:function(){
|
||||
return this.moduleCount;
|
||||
},
|
||||
make:function(){
|
||||
this.getRightType();
|
||||
this.dataCache=this.createData();
|
||||
this.createQrcode();
|
||||
},
|
||||
makeImpl:function(maskPattern){
|
||||
this.moduleCount=this.typeNumber*4+17;
|
||||
this.modules=new Array(this.moduleCount);
|
||||
for(var row=0;row<this.moduleCount;row++){
|
||||
this.modules[row]=new Array(this.moduleCount);
|
||||
}
|
||||
this.setupPositionProbePattern(0,0);
|
||||
this.setupPositionProbePattern(this.moduleCount-7,0);
|
||||
this.setupPositionProbePattern(0,this.moduleCount-7);
|
||||
this.setupPositionAdjustPattern();
|
||||
this.setupTimingPattern();
|
||||
this.setupTypeInfo(true,maskPattern);
|
||||
if(this.typeNumber>=7){
|
||||
this.setupTypeNumber(true);
|
||||
}
|
||||
this.mapData(this.dataCache,maskPattern);
|
||||
},
|
||||
setupPositionProbePattern:function(row,col){
|
||||
for(var r=-1;r<=7;r++){
|
||||
if(row+r<=-1||this.moduleCount<=row+r)continue;
|
||||
for(var c=-1;c<=7;c++){
|
||||
if(col+c<=-1||this.moduleCount<=col+c)continue;
|
||||
if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){
|
||||
this.modules[row+r][col+c]=true;
|
||||
}else{
|
||||
this.modules[row+r][col+c]=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
createQrcode:function(){
|
||||
var minLostPoint=0;
|
||||
var pattern=0;
|
||||
var bestModules=null;
|
||||
for(var i=0;i<8;i++){
|
||||
this.makeImpl(i);
|
||||
var lostPoint=QRUtil.getLostPoint(this);
|
||||
if(i==0||minLostPoint>lostPoint){
|
||||
minLostPoint=lostPoint;
|
||||
pattern=i;
|
||||
bestModules=this.modules;
|
||||
}
|
||||
}
|
||||
this.modules=bestModules;
|
||||
this.setupTypeInfo(false,pattern);
|
||||
if(this.typeNumber>=7){
|
||||
this.setupTypeNumber(false);
|
||||
}
|
||||
},
|
||||
setupTimingPattern:function(){
|
||||
for(var r=8;r<this.moduleCount-8;r++){
|
||||
if(this.modules[r][6]!=null){
|
||||
continue;
|
||||
}
|
||||
this.modules[r][6]=(r%2==0);
|
||||
if(this.modules[6][r]!=null){
|
||||
continue;
|
||||
}
|
||||
this.modules[6][r]=(r%2==0);
|
||||
}
|
||||
},
|
||||
setupPositionAdjustPattern:function(){
|
||||
var pos=QRUtil.getPatternPosition(this.typeNumber);
|
||||
for(var i=0;i<pos.length;i++){
|
||||
for(var j=0;j<pos.length;j++){
|
||||
var row=pos[i];
|
||||
var col=pos[j];
|
||||
if(this.modules[row][col]!=null){
|
||||
continue;
|
||||
}
|
||||
for(var r=-2;r<=2;r++){
|
||||
for(var c=-2;c<=2;c++){
|
||||
if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){
|
||||
this.modules[row+r][col+c]=true;
|
||||
}else{
|
||||
this.modules[row+r][col+c]=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
setupTypeNumber:function(test){
|
||||
var bits=QRUtil.getBCHTypeNumber(this.typeNumber);
|
||||
for(var i=0;i<18;i++){
|
||||
var mod=(!test&&((bits>>i)&1)==1);
|
||||
this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;
|
||||
this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;
|
||||
}
|
||||
},
|
||||
setupTypeInfo:function(test,maskPattern){
|
||||
var data=(QRErrorCorrectLevel[this.errorCorrectLevel]<<3)|maskPattern;
|
||||
var bits=QRUtil.getBCHTypeInfo(data);
|
||||
for(var i=0;i<15;i++){
|
||||
var mod=(!test&&((bits>>i)&1)==1);
|
||||
if(i<6){
|
||||
this.modules[i][8]=mod;
|
||||
}else if(i<8){
|
||||
this.modules[i+1][8]=mod;
|
||||
}else{
|
||||
this.modules[this.moduleCount-15+i][8]=mod;
|
||||
}
|
||||
var mod=(!test&&((bits>>i)&1)==1);
|
||||
if(i<8){
|
||||
this.modules[8][this.moduleCount-i-1]=mod;
|
||||
}else if(i<9){
|
||||
this.modules[8][15-i-1+1]=mod;
|
||||
}else{
|
||||
this.modules[8][15-i-1]=mod;
|
||||
}
|
||||
}
|
||||
this.modules[this.moduleCount-8][8]=(!test);
|
||||
},
|
||||
createData:function(){
|
||||
var buffer=new QRBitBuffer();
|
||||
var lengthBits=this.typeNumber>9?16:8;
|
||||
buffer.put(4,4);
|
||||
buffer.put(this.utf8bytes.length,lengthBits);
|
||||
for(var i=0,l=this.utf8bytes.length;i<l;i++){
|
||||
buffer.put(this.utf8bytes[i],8);
|
||||
}
|
||||
if(buffer.length+4<=this.totalDataCount*8){
|
||||
buffer.put(0,4);
|
||||
}
|
||||
while(buffer.length%8!=0){
|
||||
buffer.putBit(false);
|
||||
}
|
||||
while(true){
|
||||
if(buffer.length>=this.totalDataCount*8){
|
||||
break;
|
||||
}
|
||||
buffer.put(QRCodeAlg.PAD0,8);
|
||||
if(buffer.length>=this.totalDataCount*8){
|
||||
break;
|
||||
}
|
||||
buffer.put(QRCodeAlg.PAD1,8);
|
||||
}
|
||||
return this.createBytes(buffer);
|
||||
},
|
||||
createBytes:function(buffer){
|
||||
var offset=0;
|
||||
var maxDcCount=0;
|
||||
var maxEcCount=0;
|
||||
var length=this.rsBlock.length/3;
|
||||
var rsBlocks=new Array();
|
||||
for(var i=0;i<length;i++){
|
||||
var count=this.rsBlock[i*3+0];
|
||||
var totalCount=this.rsBlock[i*3+1];
|
||||
var dataCount=this.rsBlock[i*3+2];
|
||||
for(var j=0;j<count;j++){
|
||||
rsBlocks.push([dataCount,totalCount]);
|
||||
}
|
||||
}
|
||||
var dcdata=new Array(rsBlocks.length);
|
||||
var ecdata=new Array(rsBlocks.length);
|
||||
for(var r=0;r<rsBlocks.length;r++){
|
||||
var dcCount=rsBlocks[r][0];
|
||||
var ecCount=rsBlocks[r][1]-dcCount;
|
||||
maxDcCount=Math.max(maxDcCount,dcCount);
|
||||
maxEcCount=Math.max(maxEcCount,ecCount);
|
||||
dcdata[r]=new Array(dcCount);
|
||||
for(var i=0;i<dcdata[r].length;i++){
|
||||
dcdata[r][i]=0xff&buffer.buffer[i+offset];
|
||||
}
|
||||
offset+=dcCount;
|
||||
var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);
|
||||
var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);
|
||||
var modPoly=rawPoly.mod(rsPoly);
|
||||
ecdata[r]=new Array(rsPoly.getLength()-1);
|
||||
for(var i=0;i<ecdata[r].length;i++){
|
||||
var modIndex=i+modPoly.getLength()-ecdata[r].length;
|
||||
ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;
|
||||
}
|
||||
}
|
||||
var data=new Array(this.totalDataCount);
|
||||
var index=0;
|
||||
for(var i=0;i<maxDcCount;i++){
|
||||
for(var r=0;r<rsBlocks.length;r++){
|
||||
if(i<dcdata[r].length){
|
||||
data[index++]=dcdata[r][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
for(var i=0;i<maxEcCount;i++){
|
||||
for(var r=0;r<rsBlocks.length;r++){
|
||||
if(i<ecdata[r].length){
|
||||
data[index++]=ecdata[r][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
mapData:function(data,maskPattern){
|
||||
var inc=-1;
|
||||
var row=this.moduleCount-1;
|
||||
var bitIndex=7;
|
||||
var byteIndex=0;
|
||||
for(var col=this.moduleCount-1;col>0;col-=2){
|
||||
if(col==6)col--;
|
||||
while(true){
|
||||
for(var c=0;c<2;c++){
|
||||
if(this.modules[row][col-c]==null){
|
||||
var dark=false;
|
||||
if(byteIndex<data.length){
|
||||
dark=(((data[byteIndex]>>>bitIndex)&1)==1);
|
||||
}
|
||||
var mask=QRUtil.getMask(maskPattern,row,col-c);
|
||||
if(mask){
|
||||
dark=!dark;
|
||||
}
|
||||
this.modules[row][col-c]=dark;
|
||||
bitIndex--;
|
||||
if(bitIndex==-1){
|
||||
byteIndex++;
|
||||
bitIndex=7;
|
||||
}
|
||||
}
|
||||
}
|
||||
row+=inc;
|
||||
if(row<0||this.moduleCount<=row){
|
||||
row-=inc;
|
||||
inc=-inc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
QRCodeAlg.PAD0=0xEC;
|
||||
QRCodeAlg.PAD1=0x11;
|
||||
var QRErrorCorrectLevel=[1,0,3,2];
|
||||
var QRMaskPattern={
|
||||
PATTERN000:0,
|
||||
PATTERN001:1,
|
||||
PATTERN010:2,
|
||||
PATTERN011:3,
|
||||
PATTERN100:4,
|
||||
PATTERN101:5,
|
||||
PATTERN110:6,
|
||||
PATTERN111:7
|
||||
};
|
||||
var QRUtil={
|
||||
PATTERN_POSITION_TABLE:[
|
||||
[],
|
||||
[6,18],
|
||||
[6,22],
|
||||
[6,26],
|
||||
[6,30],
|
||||
[6,34],
|
||||
[6,22,38],
|
||||
[6,24,42],
|
||||
[6,26,46],
|
||||
[6,28,50],
|
||||
[6,30,54],
|
||||
[6,32,58],
|
||||
[6,34,62],
|
||||
[6,26,46,66],
|
||||
[6,26,48,70],
|
||||
[6,26,50,74],
|
||||
[6,30,54,78],
|
||||
[6,30,56,82],
|
||||
[6,30,58,86],
|
||||
[6,34,62,90],
|
||||
[6,28,50,72,94],
|
||||
[6,26,50,74,98],
|
||||
[6,30,54,78,102],
|
||||
[6,28,54,80,106],
|
||||
[6,32,58,84,110],
|
||||
[6,30,58,86,114],
|
||||
[6,34,62,90,118],
|
||||
[6,26,50,74,98,122],
|
||||
[6,30,54,78,102,126],
|
||||
[6,26,52,78,104,130],
|
||||
[6,30,56,82,108,134],
|
||||
[6,34,60,86,112,138],
|
||||
[6,30,58,86,114,142],
|
||||
[6,34,62,90,118,146],
|
||||
[6,30,54,78,102,126,150],
|
||||
[6,24,50,76,102,128,154],
|
||||
[6,28,54,80,106,132,158],
|
||||
[6,32,58,84,110,136,162],
|
||||
[6,26,54,82,110,138,166],
|
||||
[6,30,58,86,114,142,170]
|
||||
],
|
||||
G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),
|
||||
G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),
|
||||
G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),
|
||||
getBCHTypeInfo:function(data){
|
||||
var d=data<<10;
|
||||
while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){
|
||||
d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));
|
||||
}
|
||||
return((data<<10)|d)^QRUtil.G15_MASK;
|
||||
},
|
||||
getBCHTypeNumber:function(data){
|
||||
var d=data<<12;
|
||||
while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){
|
||||
d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));
|
||||
}
|
||||
return(data<<12)|d;
|
||||
},
|
||||
getBCHDigit:function(data){
|
||||
var digit=0;
|
||||
while(data!=0){
|
||||
digit++;
|
||||
data>>>=1;
|
||||
}
|
||||
return digit;
|
||||
},
|
||||
getPatternPosition:function(typeNumber){
|
||||
return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];
|
||||
},
|
||||
getMask:function(maskPattern,i,j){
|
||||
switch(maskPattern){
|
||||
case QRMaskPattern.PATTERN000:
|
||||
return(i+j)%2==0;
|
||||
case QRMaskPattern.PATTERN001:
|
||||
return i%2==0;
|
||||
case QRMaskPattern.PATTERN010:
|
||||
return j%3==0;
|
||||
case QRMaskPattern.PATTERN011:
|
||||
return(i+j)%3==0;
|
||||
case QRMaskPattern.PATTERN100:
|
||||
return(Math.floor(i/2)+Math.floor(j/3))%2==0;
|
||||
case QRMaskPattern.PATTERN101:
|
||||
return(i*j)%2+(i*j)%3==0;
|
||||
case QRMaskPattern.PATTERN110:
|
||||
return((i*j)%2+(i*j)%3)%2==0;
|
||||
case QRMaskPattern.PATTERN111:
|
||||
return((i*j)%3+(i+j)%2)%2==0;
|
||||
default:
|
||||
throw new Error("bad maskPattern:"+maskPattern);
|
||||
}
|
||||
},
|
||||
getErrorCorrectPolynomial:function(errorCorrectLength){
|
||||
var a=new QRPolynomial([1],0);
|
||||
for(var i=0;i<errorCorrectLength;i++){
|
||||
a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));
|
||||
}
|
||||
return a;
|
||||
},
|
||||
getLostPoint:function(qrCode){
|
||||
var moduleCount=qrCode.getModuleCount(),
|
||||
lostPoint=0,
|
||||
darkCount=0;
|
||||
for(var row=0;row<moduleCount;row++){
|
||||
var sameCount=0;
|
||||
var head=qrCode.modules[row][0];
|
||||
for(var col=0;col<moduleCount;col++){
|
||||
var current=qrCode.modules[row][col];
|
||||
if(col<moduleCount-6){
|
||||
if(current&&!qrCode.modules[row][col+1]&&qrCode.modules[row][col+2]&&qrCode.modules[row][col+3]&&qrCode.modules[row][col+4]&&!qrCode.modules[row][col+5]&&qrCode.modules[row][col+6]){
|
||||
if(col<moduleCount-10){
|
||||
if(qrCode.modules[row][col+7]&&qrCode.modules[row][col+8]&&qrCode.modules[row][col+9]&&qrCode.modules[row][col+10]){
|
||||
lostPoint+=40;
|
||||
}
|
||||
}else if(col>3){
|
||||
if(qrCode.modules[row][col-1]&&qrCode.modules[row][col-2]&&qrCode.modules[row][col-3]&&qrCode.modules[row][col-4]){
|
||||
lostPoint+=40;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if((row<moduleCount-1)&&(col<moduleCount-1)){
|
||||
var count=0;
|
||||
if(current)count++;
|
||||
if(qrCode.modules[row+1][col])count++;
|
||||
if(qrCode.modules[row][col+1])count++;
|
||||
if(qrCode.modules[row+1][col+1])count++;
|
||||
if(count==0||count==4){
|
||||
lostPoint+=3;
|
||||
}
|
||||
}
|
||||
if(head^current){
|
||||
sameCount++;
|
||||
}else{
|
||||
head=current;
|
||||
if(sameCount>=5){
|
||||
lostPoint+=(3+sameCount-5);
|
||||
}
|
||||
sameCount=1;
|
||||
}
|
||||
if(current){
|
||||
darkCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(var col=0;col<moduleCount;col++){
|
||||
var sameCount=0;
|
||||
var head=qrCode.modules[0][col];
|
||||
for(var row=0;row<moduleCount;row++){
|
||||
var current=qrCode.modules[row][col];
|
||||
if(row<moduleCount-6){
|
||||
if(current&&!qrCode.modules[row+1][col]&&qrCode.modules[row+2][col]&&qrCode.modules[row+3][col]&&qrCode.modules[row+4][col]&&!qrCode.modules[row+5][col]&&qrCode.modules[row+6][col]){
|
||||
if(row<moduleCount-10){
|
||||
if(qrCode.modules[row+7][col]&&qrCode.modules[row+8][col]&&qrCode.modules[row+9][col]&&qrCode.modules[row+10][col]){
|
||||
lostPoint+=40;
|
||||
}
|
||||
}else if(row>3){
|
||||
if(qrCode.modules[row-1][col]&&qrCode.modules[row-2][col]&&qrCode.modules[row-3][col]&&qrCode.modules[row-4][col]){
|
||||
lostPoint+=40;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(head^current){
|
||||
sameCount++;
|
||||
}else{
|
||||
head=current;
|
||||
if(sameCount>=5){
|
||||
lostPoint+=(3+sameCount-5);
|
||||
}
|
||||
sameCount=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;
|
||||
lostPoint+=ratio*10;
|
||||
return lostPoint;
|
||||
}
|
||||
};
|
||||
var QRMath={
|
||||
glog:function(n){
|
||||
if(n<1){
|
||||
throw new Error("glog("+n+")");
|
||||
}
|
||||
return QRMath.LOG_TABLE[n];
|
||||
},
|
||||
gexp:function(n){
|
||||
while(n<0){
|
||||
n+=255;
|
||||
}
|
||||
while(n>=256){
|
||||
n-=255;
|
||||
}
|
||||
return QRMath.EXP_TABLE[n];
|
||||
},
|
||||
EXP_TABLE:new Array(256),
|
||||
LOG_TABLE:new Array(256)
|
||||
};
|
||||
for(var i=0;i<8;i++){
|
||||
QRMath.EXP_TABLE[i]=1<<i;
|
||||
}
|
||||
for(var i=8;i<256;i++){
|
||||
QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];
|
||||
}
|
||||
for(var i=0;i<255;i++){
|
||||
QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;
|
||||
}
|
||||
function QRPolynomial(num,shift){
|
||||
if(num.length==undefined){
|
||||
throw new Error(num.length+"/"+shift);
|
||||
}
|
||||
var offset=0;
|
||||
while(offset<num.length&&num[offset]==0){
|
||||
offset++;
|
||||
}
|
||||
this.num=new Array(num.length-offset+shift);
|
||||
for(var i=0;i<num.length-offset;i++){
|
||||
this.num[i]=num[i+offset];
|
||||
}
|
||||
}
|
||||
QRPolynomial.prototype={
|
||||
get:function(index){
|
||||
return this.num[index];
|
||||
},
|
||||
getLength:function(){
|
||||
return this.num.length;
|
||||
},
|
||||
multiply:function(e){
|
||||
var num=new Array(this.getLength()+e.getLength()-1);
|
||||
for(var i=0;i<this.getLength();i++){
|
||||
for(var j=0;j<e.getLength();j++){
|
||||
num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));
|
||||
}
|
||||
}
|
||||
return new QRPolynomial(num,0);
|
||||
},
|
||||
mod:function(e){
|
||||
var tl=this.getLength(),
|
||||
el=e.getLength();
|
||||
if(tl-el<0){
|
||||
return this;
|
||||
}
|
||||
var num=new Array(tl);
|
||||
for(var i=0;i<tl;i++){
|
||||
num[i]=this.get(i);
|
||||
}
|
||||
while(num.length>=el){
|
||||
var ratio=QRMath.glog(num[0])-QRMath.glog(e.get(0));
|
||||
for(var i=0;i<e.getLength();i++){
|
||||
num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);
|
||||
}
|
||||
while(num[0]==0){
|
||||
num.shift();
|
||||
}
|
||||
}
|
||||
return new QRPolynomial(num,0);
|
||||
}
|
||||
};
|
||||
var RS_BLOCK_TABLE=[
|
||||
[1,26,19],
|
||||
[1,26,16],
|
||||
[1,26,13],
|
||||
[1,26,9],
|
||||
[1,44,34],
|
||||
[1,44,28],
|
||||
[1,44,22],
|
||||
[1,44,16],
|
||||
[1,70,55],
|
||||
[1,70,44],
|
||||
[2,35,17],
|
||||
[2,35,13],
|
||||
[1,100,80],
|
||||
[2,50,32],
|
||||
[2,50,24],
|
||||
[4,25,9],
|
||||
[1,134,108],
|
||||
[2,67,43],
|
||||
[2,33,15,2,34,16],
|
||||
[2,33,11,2,34,12],
|
||||
[2,86,68],
|
||||
[4,43,27],
|
||||
[4,43,19],
|
||||
[4,43,15],
|
||||
[2,98,78],
|
||||
[4,49,31],
|
||||
[2,32,14,4,33,15],
|
||||
[4,39,13,1,40,14],
|
||||
[2,121,97],
|
||||
[2,60,38,2,61,39],
|
||||
[4,40,18,2,41,19],
|
||||
[4,40,14,2,41,15],
|
||||
[2,146,116],
|
||||
[3,58,36,2,59,37],
|
||||
[4,36,16,4,37,17],
|
||||
[4,36,12,4,37,13],
|
||||
[2,86,68,2,87,69],
|
||||
[4,69,43,1,70,44],
|
||||
[6,43,19,2,44,20],
|
||||
[6,43,15,2,44,16],
|
||||
[4,101,81],
|
||||
[1,80,50,4,81,51],
|
||||
[4,50,22,4,51,23],
|
||||
[3,36,12,8,37,13],
|
||||
[2,116,92,2,117,93],
|
||||
[6,58,36,2,59,37],
|
||||
[4,46,20,6,47,21],
|
||||
[7,42,14,4,43,15],
|
||||
[4,133,107],
|
||||
[8,59,37,1,60,38],
|
||||
[8,44,20,4,45,21],
|
||||
[12,33,11,4,34,12],
|
||||
[3,145,115,1,146,116],
|
||||
[4,64,40,5,65,41],
|
||||
[11,36,16,5,37,17],
|
||||
[11,36,12,5,37,13],
|
||||
[5,109,87,1,110,88],
|
||||
[5,65,41,5,66,42],
|
||||
[5,54,24,7,55,25],
|
||||
[11,36,12],
|
||||
[5,122,98,1,123,99],
|
||||
[7,73,45,3,74,46],
|
||||
[15,43,19,2,44,20],
|
||||
[3,45,15,13,46,16],
|
||||
[1,135,107,5,136,108],
|
||||
[10,74,46,1,75,47],
|
||||
[1,50,22,15,51,23],
|
||||
[2,42,14,17,43,15],
|
||||
[5,150,120,1,151,121],
|
||||
[9,69,43,4,70,44],
|
||||
[17,50,22,1,51,23],
|
||||
[2,42,14,19,43,15],
|
||||
[3,141,113,4,142,114],
|
||||
[3,70,44,11,71,45],
|
||||
[17,47,21,4,48,22],
|
||||
[9,39,13,16,40,14],
|
||||
[3,135,107,5,136,108],
|
||||
[3,67,41,13,68,42],
|
||||
[15,54,24,5,55,25],
|
||||
[15,43,15,10,44,16],
|
||||
[4,144,116,4,145,117],
|
||||
[17,68,42],
|
||||
[17,50,22,6,51,23],
|
||||
[19,46,16,6,47,17],
|
||||
[2,139,111,7,140,112],
|
||||
[17,74,46],
|
||||
[7,54,24,16,55,25],
|
||||
[34,37,13],
|
||||
[4,151,121,5,152,122],
|
||||
[4,75,47,14,76,48],
|
||||
[11,54,24,14,55,25],
|
||||
[16,45,15,14,46,16],
|
||||
[6,147,117,4,148,118],
|
||||
[6,73,45,14,74,46],
|
||||
[11,54,24,16,55,25],
|
||||
[30,46,16,2,47,17],
|
||||
[8,132,106,4,133,107],
|
||||
[8,75,47,13,76,48],
|
||||
[7,54,24,22,55,25],
|
||||
[22,45,15,13,46,16],
|
||||
[10,142,114,2,143,115],
|
||||
[19,74,46,4,75,47],
|
||||
[28,50,22,6,51,23],
|
||||
[33,46,16,4,47,17],
|
||||
[8,152,122,4,153,123],
|
||||
[22,73,45,3,74,46],
|
||||
[8,53,23,26,54,24],
|
||||
[12,45,15,28,46,16],
|
||||
[3,147,117,10,148,118],
|
||||
[3,73,45,23,74,46],
|
||||
[4,54,24,31,55,25],
|
||||
[11,45,15,31,46,16],
|
||||
[7,146,116,7,147,117],
|
||||
[21,73,45,7,74,46],
|
||||
[1,53,23,37,54,24],
|
||||
[19,45,15,26,46,16],
|
||||
[5,145,115,10,146,116],
|
||||
[19,75,47,10,76,48],
|
||||
[15,54,24,25,55,25],
|
||||
[23,45,15,25,46,16],
|
||||
[13,145,115,3,146,116],
|
||||
[2,74,46,29,75,47],
|
||||
[42,54,24,1,55,25],
|
||||
[23,45,15,28,46,16],
|
||||
[17,145,115],
|
||||
[10,74,46,23,75,47],
|
||||
[10,54,24,35,55,25],
|
||||
[19,45,15,35,46,16],
|
||||
[17,145,115,1,146,116],
|
||||
[14,74,46,21,75,47],
|
||||
[29,54,24,19,55,25],
|
||||
[11,45,15,46,46,16],
|
||||
[13,145,115,6,146,116],
|
||||
[14,74,46,23,75,47],
|
||||
[44,54,24,7,55,25],
|
||||
[59,46,16,1,47,17],
|
||||
[12,151,121,7,152,122],
|
||||
[12,75,47,26,76,48],
|
||||
[39,54,24,14,55,25],
|
||||
[22,45,15,41,46,16],
|
||||
[6,151,121,14,152,122],
|
||||
[6,75,47,34,76,48],
|
||||
[46,54,24,10,55,25],
|
||||
[2,45,15,64,46,16],
|
||||
[17,152,122,4,153,123],
|
||||
[29,74,46,14,75,47],
|
||||
[49,54,24,10,55,25],
|
||||
[24,45,15,46,46,16],
|
||||
[4,152,122,18,153,123],
|
||||
[13,74,46,32,75,47],
|
||||
[48,54,24,14,55,25],
|
||||
[42,45,15,32,46,16],
|
||||
[20,147,117,4,148,118],
|
||||
[40,75,47,7,76,48],
|
||||
[43,54,24,22,55,25],
|
||||
[10,45,15,67,46,16],
|
||||
[19,148,118,6,149,119],
|
||||
[18,75,47,31,76,48],
|
||||
[34,54,24,34,55,25],
|
||||
[20,45,15,61,46,16]
|
||||
];
|
||||
QRCodeAlg.prototype.getRightType=function(){
|
||||
for(var typeNumber=1;typeNumber<41;typeNumber++){
|
||||
var rsBlock=RS_BLOCK_TABLE[(typeNumber-1)*4+this.errorCorrectLevel];
|
||||
if(rsBlock==undefined){
|
||||
throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+this.errorCorrectLevel);
|
||||
}
|
||||
var length=rsBlock.length/3;
|
||||
var totalDataCount=0;
|
||||
for(var i=0;i<length;i++){
|
||||
var count=rsBlock[i*3+0];
|
||||
var dataCount=rsBlock[i*3+2];
|
||||
totalDataCount+=dataCount*count;
|
||||
}
|
||||
var lengthBytes=typeNumber>9?2:1;
|
||||
if(this.utf8bytes.length+lengthBytes<totalDataCount||typeNumber==40){
|
||||
this.typeNumber=typeNumber;
|
||||
this.rsBlock=rsBlock;
|
||||
this.totalDataCount=totalDataCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
function QRBitBuffer(){
|
||||
this.buffer=new Array();
|
||||
this.length=0;
|
||||
}
|
||||
QRBitBuffer.prototype={
|
||||
get:function(index){
|
||||
var bufIndex=Math.floor(index/8);
|
||||
return((this.buffer[bufIndex]>>>(7-index%8))&1);
|
||||
},
|
||||
put:function(num,length){
|
||||
for(var i=0;i<length;i++){
|
||||
this.putBit(((num>>>(length-i-1))&1));
|
||||
}
|
||||
},
|
||||
putBit:function(bit){
|
||||
var bufIndex=Math.floor(this.length/8);
|
||||
if(this.buffer.length<=bufIndex){
|
||||
this.buffer.push(0);
|
||||
}
|
||||
if(bit){
|
||||
this.buffer[bufIndex]|=(0x80>>>(this.length%8));
|
||||
}
|
||||
this.length++;
|
||||
}
|
||||
};
|
||||
let qrcodeAlgObjCache=[];
|
||||
QRCode=function(opt){
|
||||
this.options={
|
||||
text:'',
|
||||
size:256,
|
||||
correctLevel:3,
|
||||
background:'#ffffff',
|
||||
foreground:'#000000',
|
||||
pdground:'#000000',
|
||||
image:'',
|
||||
imageSize:30,
|
||||
canvasId:opt.canvasId,
|
||||
context:opt.context,
|
||||
usingComponents:opt.usingComponents,
|
||||
showLoading:opt.showLoading,
|
||||
loadingText:opt.loadingText,
|
||||
};
|
||||
if(typeof opt==='string'){
|
||||
opt={
|
||||
text:opt
|
||||
};
|
||||
}
|
||||
if(opt){
|
||||
for(var i in opt){
|
||||
this.options[i]=opt[i];
|
||||
}
|
||||
}
|
||||
var qrCodeAlg=null;
|
||||
for(var i=0,l=qrcodeAlgObjCache.length;i<l;i++){
|
||||
if(qrcodeAlgObjCache[i].text==this.options.text&&qrcodeAlgObjCache[i].text.correctLevel==this.options.correctLevel){
|
||||
qrCodeAlg=qrcodeAlgObjCache[i].obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i==l){
|
||||
qrCodeAlg=new QRCodeAlg(this.options.text,this.options.correctLevel);
|
||||
qrcodeAlgObjCache.push({
|
||||
text:this.options.text,
|
||||
correctLevel:this.options.correctLevel,
|
||||
obj:qrCodeAlg
|
||||
});
|
||||
}
|
||||
let getForeGround=function(config){
|
||||
var options=config.options;
|
||||
if(options.pdground&&(
|
||||
(config.row>1&&config.row<5&&config.col>1&&config.col<5)||
|
||||
(config.row>(config.count-6)&&config.row<(config.count-2)&&config.col>1&&config.col<5)||
|
||||
(config.row>1&&config.row<5&&config.col>(config.count-6)&&config.col<(config.count-2))
|
||||
)){
|
||||
return options.pdground;
|
||||
}
|
||||
return options.foreground;
|
||||
}
|
||||
let createCanvas=function(options){
|
||||
if(options.showLoading){
|
||||
uni.showLoading({
|
||||
title:options.loadingText,
|
||||
mask:true
|
||||
});
|
||||
}
|
||||
var ctx=uni.createCanvasContext(options.canvasId,options.context);
|
||||
var count=qrCodeAlg.getModuleCount();
|
||||
var ratioSize=options.size;
|
||||
var ratioImgSize=options.imageSize;
|
||||
var tileW=(ratioSize/count).toPrecision(4);
|
||||
var tileH=(ratioSize/count).toPrecision(4);
|
||||
for(var row=0;row<count;row++){
|
||||
for(var col=0;col<count;col++){
|
||||
var w=(Math.ceil((col+1)*tileW)-Math.floor(col*tileW));
|
||||
var h=(Math.ceil((row+1)*tileW)-Math.floor(row*tileW));
|
||||
var foreground=getForeGround({
|
||||
row:row,
|
||||
col:col,
|
||||
count:count,
|
||||
options:options
|
||||
});
|
||||
ctx.setFillStyle(qrCodeAlg.modules[row][col]?foreground:options.background);
|
||||
ctx.fillRect(Math.round(col*tileW),Math.round(row*tileH),w,h);
|
||||
}
|
||||
}
|
||||
if(options.image){
|
||||
var x=Number(((ratioSize-ratioImgSize)/2).toFixed(2));
|
||||
var y=Number(((ratioSize-ratioImgSize)/2).toFixed(2));
|
||||
drawRoundedRect(ctx,x,y,ratioImgSize,ratioImgSize,2,6,true,true)
|
||||
ctx.drawImage(options.image,x,y,ratioImgSize,ratioImgSize);
|
||||
function drawRoundedRect(ctxi,x,y,width,height,r,lineWidth,fill,stroke){
|
||||
ctxi.setLineWidth(lineWidth);
|
||||
ctxi.setFillStyle(options.background);
|
||||
ctxi.setStrokeStyle(options.background);
|
||||
ctxi.beginPath();
|
||||
ctxi.moveTo(x+r,y);
|
||||
ctxi.arcTo(x+width,y,x+width,y+r,r);
|
||||
ctxi.arcTo(x+width,y+height,x+width-r,y+height,r);
|
||||
ctxi.arcTo(x,y+height,x,y+height-r,r);
|
||||
ctxi.arcTo(x,y,x+r,y,r);
|
||||
ctxi.closePath();
|
||||
if(fill){
|
||||
ctxi.fill();
|
||||
}
|
||||
if(stroke){
|
||||
ctxi.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(()=>{
|
||||
ctx.draw(true,()=>{
|
||||
setTimeout(()=>{
|
||||
uni.canvasToTempFilePath({
|
||||
width:options.width,
|
||||
height:options.height,
|
||||
destWidth:options.width,
|
||||
destHeight:options.height,
|
||||
canvasId:options.canvasId,
|
||||
quality:Number(1),
|
||||
success:function(res){
|
||||
if(options.cbResult){
|
||||
options.cbResult(res.tempFilePath)
|
||||
}
|
||||
},
|
||||
fail:function(res){
|
||||
if(options.cbResult){
|
||||
options.cbResult(res)
|
||||
}
|
||||
},
|
||||
complete:function(){
|
||||
if(options.showLoading){
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
},options.context);
|
||||
},options.text.length+100);
|
||||
});
|
||||
},options.usingComponents?0:150);
|
||||
}
|
||||
createCanvas(this.options);
|
||||
let empty=function(v){
|
||||
let tp=typeof v,
|
||||
rt=false;
|
||||
if(tp=="number"&&String(v)==""){
|
||||
rt=true
|
||||
}else if(tp=="undefined"){
|
||||
rt=true
|
||||
}else if(tp=="object"){
|
||||
if(JSON.stringify(v)=="{}"||JSON.stringify(v)=="[]"||v==null)rt=true
|
||||
}else if(tp=="string"){
|
||||
if(v==""||v=="undefined"||v=="null"||v=="{}"||v=="[]")rt=true
|
||||
}else if(tp=="function"){
|
||||
rt=false
|
||||
}
|
||||
return rt
|
||||
}
|
||||
};
|
||||
QRCode.prototype.clear=function(fn){
|
||||
var ctx=uni.createCanvasContext(this.options.canvasId,this.options.context)
|
||||
ctx.clearRect(0,0,this.options.size,this.options.size)
|
||||
ctx.draw(false,()=>{
|
||||
if(fn){
|
||||
fn()
|
||||
}
|
||||
})
|
||||
};
|
||||
})()
|
||||
export default QRCode
|
||||
210
components/zb-code/zb-code.vue
Normal file
210
components/zb-code/zb-code.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template xlang="wxml" minapp="mpvue">
|
||||
<view class="zb-code">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<canvas class="zb-code-canvas" :canvas-id="cid" :style="{width:cpSize+'px',height:cpSize+'px'}" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<canvas :id="cid" :width="cpSize" :height="cpSize" class="zb-code-canvas" />
|
||||
<!-- #endif -->
|
||||
<image v-show="show" :src="result" :style="{width:cpSize+'px',height:cpSize+'px'}" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import QRCode from "./qrcode.js"
|
||||
let qrcode
|
||||
export default {
|
||||
name: "zb-code",
|
||||
props: {
|
||||
cid: {
|
||||
type: String,
|
||||
default: 'zb-code-canvas'
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
default: 'upx'
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
val: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: '#ffffff'
|
||||
},
|
||||
foreground: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
},
|
||||
pdground: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 40
|
||||
},
|
||||
lv: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
onval: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadMake: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
usingComponents: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showLoading: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loadingText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_makeCode() {
|
||||
let that = this
|
||||
if (!this._empty(this.val)) {
|
||||
qrcode = new QRCode({
|
||||
context: that, // 上下文环境
|
||||
canvasId:that.cid, // canvas-id
|
||||
usingComponents: that.usingComponents, // 是否是自定义组件
|
||||
showLoading: that.showLoading, // 是否显示loading
|
||||
loadingText: that.loadingText, // loading文字
|
||||
text: that.val, // 生成内容
|
||||
size: that.cpSize, // 二维码大小
|
||||
background: that.background, // 背景色
|
||||
foreground: that.foreground, // 前景色
|
||||
pdground: that.pdground, // 定位角点颜色
|
||||
correctLevel: that.lv, // 容错级别
|
||||
image: that.icon, // 二维码图标
|
||||
imageSize: that.iconSize,// 二维码图标大小
|
||||
cbResult: function (res) { // 生成二维码的回调
|
||||
that._result(res)
|
||||
},
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '二维码内容不能为空',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
},
|
||||
_clearCode() {
|
||||
this._result('')
|
||||
qrcode.clear()
|
||||
},
|
||||
_saveCode() {
|
||||
let that = this;
|
||||
if (this.result != "") {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: that.result,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: '二维码保存成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
_result(res) {
|
||||
this.result = res;
|
||||
this.$emit('result', res)
|
||||
},
|
||||
_empty(v) {
|
||||
let tp = typeof v,
|
||||
rt = false;
|
||||
if (tp == "number" && String(v) == "") {
|
||||
rt = true
|
||||
} else if (tp == "undefined") {
|
||||
rt = true
|
||||
} else if (tp == "object") {
|
||||
if (JSON.stringify(v) == "{}" || JSON.stringify(v) == "[]" || v == null) rt = true
|
||||
} else if (tp == "string") {
|
||||
if (v == "" || v == "undefined" || v == "null" || v == "{}" || v == "[]") rt = true
|
||||
} else if (tp == "function") {
|
||||
rt = false
|
||||
}
|
||||
return rt
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
size: function (n, o) {
|
||||
if (n != o && !this._empty(n)) {
|
||||
this.cSize = n
|
||||
if (!this._empty(this.val)) {
|
||||
setTimeout(() => {
|
||||
this._makeCode()
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
},
|
||||
val: function (n, o) {
|
||||
if (this.onval) {
|
||||
if (n != o && !this._empty(n)) {
|
||||
setTimeout(() => {
|
||||
this._makeCode()
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cpSize() {
|
||||
if(this.unit == "upx"){
|
||||
return uni.upx2px(this.size)
|
||||
}else{
|
||||
return this.size
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
if (this.loadMake) {
|
||||
if (!this._empty(this.val)) {
|
||||
setTimeout(() => {
|
||||
this._makeCode()
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.zb-code {
|
||||
position: relative;
|
||||
}
|
||||
.zb-code-canvas {
|
||||
position: fixed;
|
||||
top: -99999upx;
|
||||
left: -99999upx;
|
||||
z-index: -99999;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user