feat: 添加静态资源文件及基础组件

- 新增大量图片资源,包括图标、背景图、海报等,用于页面展示
- 添加字体文件及样式配置,支持自定义字体
- 引入 uni-ui 组件库,包括图标、弹窗、日期选择器等常用组件
- 添加网络状态监听、加密工具等基础工具函数
- 配置 Vuex 状态管理,包括购物车数量、热门搜索词等
- 增加页面组件,如用户协议、分销信息、提现状态等
- 配置项目构建和忽略文件,优化开发体验
This commit is contained in:
2026-02-26 17:59:51 +08:00
commit a5c5d753b6
720 changed files with 178840 additions and 0 deletions

395
components/BaseCalendar.vue Normal file
View 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
View 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
View 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>

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

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

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

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

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

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

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

View File

@@ -0,0 +1,3 @@
<template>
<view></view>
</template>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View 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(/&nbsp;/g, '\u00A0').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&amp;/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>

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

View 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(/&amp;/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); // 解决 &nbsp; 失效
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(/&quot;/g, '"').replace(/&amp;/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;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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

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

View 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

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