我的页和设置页完成
@@ -12,9 +12,15 @@ ENV = 'development'
|
|||||||
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
|
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
|
||||||
|
|
||||||
app入口
|
app入口
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
# VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001'
|
# VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001'
|
||||||
# VITE_APP_BASE_API_Url = 'http://25.16.122.65:7001'
|
# VITE_APP_BASE_API_Url = 'http://25.16.122.65:7001'
|
||||||
# VITE_APP_BASE_API_Url = 'http://25.18.122.66:9786'
|
# VITE_APP_BASE_API_Url = 'http://25.18.122.66:9786'
|
||||||
|
|
||||||
|
=======
|
||||||
|
# VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001'
|
||||||
|
# VITE_APP_BASE_API_Url = 'http://25.16.122.65:7001'
|
||||||
|
# VITE_APP_BASE_API_Url = 'http://25.18.122.66:9786'
|
||||||
|
>>>>>>> cea58939be69e08335a0aaf3f8fef73c5fdb914a
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -62,7 +62,54 @@ export function downloadFile(id) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 上传音频 翻译文本
|
||||||
|
export const uploadVoiceFile = (file) => {
|
||||||
|
const url = '/trastudy/traStdyInfo/audioTranscriptions';
|
||||||
|
const base_url = get_base_url(url);
|
||||||
|
const token = getToken();
|
||||||
|
const header = {
|
||||||
|
"Content-Type": "multipart/form-data; charset=UTF-8",
|
||||||
|
};
|
||||||
|
if (token)
|
||||||
|
header['summary'] = token
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.uploadFile({
|
||||||
|
url: `${base_url}${url}`,
|
||||||
|
header: header,
|
||||||
|
filePath: file,
|
||||||
|
name: 'voice',
|
||||||
|
success(result) {
|
||||||
|
resolve(result)
|
||||||
|
},
|
||||||
|
fail(error) {
|
||||||
|
reject(new Error('Upload failed'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 获取音频 通用
|
||||||
|
export function downloadVoiceFile(url) {
|
||||||
|
const token = getToken();
|
||||||
|
const header = {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
|
};
|
||||||
|
if (token)
|
||||||
|
header['summary'] = token
|
||||||
|
let baseUrl = get_base_url();
|
||||||
|
let _params = new FormData()
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.downloadFile({
|
||||||
|
url: `${baseUrl}${url}`,
|
||||||
|
header,
|
||||||
|
success(result) {
|
||||||
|
resolve(result)
|
||||||
|
},
|
||||||
|
fail(error) {
|
||||||
|
reject(new Error(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
// 对象存储预览 /traoss/tras3/show/{文件ID}
|
// 对象存储预览 /traoss/tras3/show/{文件ID}
|
||||||
export function previewFile(id, responseType = 'arraybuffer') {
|
export function previewFile(id, responseType = 'arraybuffer') {
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
|
|||||||
@@ -2,11 +2,36 @@ import request from '@/api/request'
|
|||||||
|
|
||||||
const base_url = '/trastudy'
|
const base_url = '/trastudy'
|
||||||
|
|
||||||
// 获取课程分类树接口
|
// 获取课程学习信息接口
|
||||||
export const getCourseStudyInfoApi = (data) => {
|
export const getCourseStudyInfoApi = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: base_url + '/traStdyInfo/queryTraStdyProgressAndTeacher',
|
url: base_url + '/traStdyInfo/queryTraStdyProgressAndTeacher',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取课程学习 知识点 接口
|
||||||
|
export const queryTraStdyBatchWaitParagraphInfoApi = (data) => {
|
||||||
|
return request({
|
||||||
|
url: base_url + '/traStdyInfo/queryTraStdyBatchWaitParagraphInfo',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 学习段落完成
|
||||||
|
export const studyParagraphOverApi = (data) => {
|
||||||
|
return request({
|
||||||
|
url: base_url + '/traStdyInfo/studyParagraphOver',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 上传语音翻译i文本 /traStdyInfo/audioTranscriptions
|
||||||
|
export const audioTranscriptionsApi = (data) => {
|
||||||
|
return request({
|
||||||
|
url: base_url + '/traStdyInfo/audioTranscriptions',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
};
|
};
|
||||||
@@ -36,6 +36,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/welcome/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "欢迎",
|
||||||
|
"app-plus": {
|
||||||
|
"titleNView": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/study/index",
|
"path": "pages/study/index",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<template>
|
||||||
|
<view class="chart-box">
|
||||||
|
<view class="cont-box">
|
||||||
|
<view v-for="width in 3" :class="`percent-${width * 25}`"></view>
|
||||||
|
<view class="data-box">
|
||||||
|
<view class="line-box">
|
||||||
|
<view v-for="width in 3" :class="`percent-${width * 25}`"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="title">
|
||||||
|
<img class="title-icon" src="@/static/images/course/edit.png" alt="" />
|
||||||
|
改进建议:
|
||||||
|
</view>
|
||||||
|
<view class="total-score">
|
||||||
|
<view class="score-num">57</view>分
|
||||||
|
<img class="total-score-icon" src="@/static/images/course/score.png" alt="" />
|
||||||
|
</view>
|
||||||
|
<view v-for="item,index in 4" :class="['score-item', 'score-item'+(index + 1)]">
|
||||||
|
<view class="score-item-name">词命中率</view>
|
||||||
|
<view class="score-item-score">98</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed} from "vue"
|
||||||
|
const props = defineProps({
|
||||||
|
data:{
|
||||||
|
default:() => [60,70,80,90],
|
||||||
|
type: Array
|
||||||
|
},
|
||||||
|
size:{
|
||||||
|
default: 262,
|
||||||
|
type: [Number,String]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const chartWidth = computed(() => props.size + 'rpx')
|
||||||
|
const chartHeight = computed(() => props.size + 'rpx')
|
||||||
|
const scoreMarginX = computed(() => {
|
||||||
|
return props.size/2 + 'rpx'
|
||||||
|
})
|
||||||
|
const scoreMarginY = computed(() => {
|
||||||
|
return (props.size/2 + 30) + 'rpx'
|
||||||
|
})
|
||||||
|
const scoreMarginAbsX = computed(() => {
|
||||||
|
return '-' + (props.size/2 ) + 'rpx'
|
||||||
|
})
|
||||||
|
const scoreMarginAbsY = computed(() => {
|
||||||
|
return '-' + (props.size/2 - 25) + 'rpx'
|
||||||
|
})
|
||||||
|
const styleFour = ref('polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)')
|
||||||
|
const styleFourResult = computed(()=>{
|
||||||
|
let score = props.data
|
||||||
|
let _tx = 50
|
||||||
|
let _ty = 50 - Math.floor((score[0]/100)*50)
|
||||||
|
let _rx = 50 + Math.floor((score[1]/100)*50)
|
||||||
|
let _ry = 50
|
||||||
|
let _bx = 50
|
||||||
|
let _by = 50 + Math.floor((score[2]/100)*50)
|
||||||
|
let _lx = 50 - Math.floor((score[3]/100)*50)
|
||||||
|
let _ly = 50
|
||||||
|
return `polygon(${_tx}% ${_ty}%, ${_rx}% ${_ry}%, ${_bx}% ${_by}%, ${_lx}% ${_ly}%)`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-box{
|
||||||
|
width: 100%;
|
||||||
|
height: 580rpx;
|
||||||
|
background:linear-gradient( 270deg, #4a74ff 0%, #6fa7fe 100%);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
.cont-box{
|
||||||
|
background-color: #87abfe;
|
||||||
|
width: v-bind(chartWidth);
|
||||||
|
height: v-bind(chartHeight);
|
||||||
|
clip-path: v-bind(styleFour);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
margin-top: 25rpx;
|
||||||
|
&::before{
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 3rpx;
|
||||||
|
background-color: rgba(255, 255, 255, .3);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
&::after{
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
width: 3rpx;
|
||||||
|
background-color: rgba(255, 255, 255, .3);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
@for $i from 1 through 3 {
|
||||||
|
.percent-#{$i*25}{
|
||||||
|
width: $i * 25%;
|
||||||
|
height: $i * 25%;
|
||||||
|
clip-path: v-bind(styleFour);
|
||||||
|
background-color: rgba(255, 255, 255, .3);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: (4 - $i) * 2;
|
||||||
|
&::before{
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 10rpx);
|
||||||
|
height: calc(100% - 10rpx);
|
||||||
|
background-color: #87abfe;
|
||||||
|
clip-path: v-bind(styleFour);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.data-box{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
clip-path: v-bind(styleFourResult);
|
||||||
|
position: absolute;
|
||||||
|
background-color: #fff;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 12;
|
||||||
|
overflow: hidden;
|
||||||
|
.line-box{
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 24rpx);
|
||||||
|
height: calc(100% - 24rpx);
|
||||||
|
// background-color: #6691fe;
|
||||||
|
background-color: #6691fe;
|
||||||
|
clip-path: v-bind(styleFourResult);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 through 3 {
|
||||||
|
.percent-#{$i*25}{
|
||||||
|
background-color: rgba(255, 255, 255, .1);
|
||||||
|
z-index: (4 - $i) * 2 + 10;
|
||||||
|
&::before{
|
||||||
|
background-color: #6692ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title{
|
||||||
|
padding: 24rpx;
|
||||||
|
line-height: 46rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #fff;
|
||||||
|
.title-icon{
|
||||||
|
width: 27rpx;
|
||||||
|
height: 27rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.total-score{
|
||||||
|
position: absolute;
|
||||||
|
right: 30rpx;
|
||||||
|
top:30rpx;
|
||||||
|
width: 100rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 70rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-shadow: 0 4px 12px rgba(0, 0, 0, .33);
|
||||||
|
.score-num{
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 60rpx;
|
||||||
|
line-height: 70rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.total-score-icon{
|
||||||
|
width: 100%;
|
||||||
|
height: 16rpx;
|
||||||
|
position: absolute;
|
||||||
|
bottom:0;
|
||||||
|
left:0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.score-item{
|
||||||
|
position: absolute;
|
||||||
|
color: #fff;
|
||||||
|
width: 120rpx;
|
||||||
|
text-align: center;
|
||||||
|
.score-item-name{
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
.score-item-score{
|
||||||
|
font-size: 38rpx;
|
||||||
|
line-height: 46rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
&.score-item1{
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -60rpx;
|
||||||
|
bottom: 50%;
|
||||||
|
transform: translateY(v-bind(scoreMarginAbsY));
|
||||||
|
}
|
||||||
|
&.score-item2{
|
||||||
|
left: 0%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(calc(v-bind(scoreMarginX) - 60rpx), calc(-50% + 25rpx));
|
||||||
|
}
|
||||||
|
&.score-item3{
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -60rpx;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(v-bind(scoreMarginY));
|
||||||
|
}
|
||||||
|
&.score-item4{
|
||||||
|
right: 0%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(calc(v-bind(scoreMarginAbsX) + 60rpx), calc(-50% + 25rpx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
<template>
|
||||||
|
<view class="chart-box">
|
||||||
|
<view class="cont-box">
|
||||||
|
<view class="total-line total-line1"></view>
|
||||||
|
<view class="total-line total-line2"></view>
|
||||||
|
<view class="total-line total-line3"></view>
|
||||||
|
<view v-for="width in 3" :class="`percent-${width * 25}`"></view>
|
||||||
|
<view class="data-box">
|
||||||
|
<view class="line-box">
|
||||||
|
<view v-for="width in 3" :class="`percent-${width * 25}`"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="title">
|
||||||
|
<img class="title-icon" src="@/static/images/course/edit.png" alt="" />
|
||||||
|
改进建议:
|
||||||
|
</view>
|
||||||
|
<view class="total-score">
|
||||||
|
<view class="score-num">57</view>分
|
||||||
|
<img class="total-score-icon" src="@/static/images/course/score.png" alt="" />
|
||||||
|
</view>
|
||||||
|
<view v-for="item,index in 3" :class="['score-item', 'score-item'+(index + 1)]">
|
||||||
|
<view class="score-item-name">词命中率</view>
|
||||||
|
<view class="score-item-score">98</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed} from "vue"
|
||||||
|
const props = defineProps({
|
||||||
|
data:{
|
||||||
|
default:() => [60,70,80,90],
|
||||||
|
type: Array
|
||||||
|
},
|
||||||
|
size:{
|
||||||
|
default: 262,
|
||||||
|
type: [Number,String]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let _scale = .9
|
||||||
|
const chartWidth = computed(() => props.size / _scale + 'rpx')
|
||||||
|
const chartHeight = computed(() => props.size / _scale + 'rpx')
|
||||||
|
|
||||||
|
const scoreMarginX = computed(() => {
|
||||||
|
return (props.size/2 + 20) + 'rpx'
|
||||||
|
})
|
||||||
|
const scoreMarginAbsY = computed(() => {
|
||||||
|
return '-' + (props.size/2 - 40) + 'rpx'
|
||||||
|
})
|
||||||
|
const scoreMarginPercentY = computed(() => {
|
||||||
|
let score = props.data
|
||||||
|
return setPercent(score[2], Math.round(100 * 1.732 / 6 * _scale * 100)/100) + '%'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const styleFour = computed(()=>{
|
||||||
|
// let _blank = (50 - Math.round(100 * 1.732 / 3))/2
|
||||||
|
// let _margin = _blank +
|
||||||
|
let _tx = 50
|
||||||
|
let _ty = 50 - Math.round(100 * 1.732 / 3)* _scale
|
||||||
|
let _rx = 50 + 50 *_scale
|
||||||
|
let _ry = 50 + Math.round(100 * 1.732 / 6)* _scale
|
||||||
|
let _bx = 50 - 50 *_scale
|
||||||
|
let _by = 50 + Math.round(100 * 1.732 / 6)* _scale
|
||||||
|
return `polygon(${_tx}% ${_ty}%, ${_rx}% ${_ry}%, ${_bx}% ${_by}%)`
|
||||||
|
})
|
||||||
|
const styleFourResult = computed(()=>{
|
||||||
|
let score = props.data
|
||||||
|
let _tx = 50
|
||||||
|
let _ty = 50 - setPercent(score[0], Math.round(100 * 1.732 / 3 * _scale * 100)/100)
|
||||||
|
let _rx = 50 + setPercent(score[1], Math.round(50 *_scale * 100)/100)
|
||||||
|
let _ry = 50 + setPercent(score[1], Math.round(100 * 1.732 / 6 * _scale * 100)/100)
|
||||||
|
let _bx = 50 - setPercent(score[2], Math.round(50 *_scale * 100)/100)
|
||||||
|
let _by = 50 + setPercent(score[2], Math.round(100 * 1.732 / 6 * _scale * 100)/100)
|
||||||
|
return `polygon(${_tx}% ${_ty}%, ${_rx}% ${_ry}%, ${_bx}% ${_by}%)`
|
||||||
|
})
|
||||||
|
const setPercent = (precent, show) => {
|
||||||
|
return Math.floor((precent/100) * show)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-box{
|
||||||
|
width: 100%;
|
||||||
|
height: 580rpx;
|
||||||
|
background:linear-gradient( 270deg, #4a74ff 0%, #6fa7fe 100%);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
.cont-box{
|
||||||
|
background-color: #87abfe;
|
||||||
|
width: v-bind(chartWidth);
|
||||||
|
height: v-bind(chartHeight);
|
||||||
|
clip-path: v-bind(styleFour);
|
||||||
|
position: absolute;
|
||||||
|
top: calc(60% + 20rpx);
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
.total-line{
|
||||||
|
width: 57.7%;
|
||||||
|
height: 3rpx;
|
||||||
|
background-color: rgba(255, 255, 255, .3);
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 20;
|
||||||
|
&.total-line1{
|
||||||
|
top: 64.5%;
|
||||||
|
left: 46.5%;
|
||||||
|
transform: rotateZ(30deg);
|
||||||
|
}
|
||||||
|
&.total-line2{
|
||||||
|
top: 64.5%;
|
||||||
|
left:-4.5%;
|
||||||
|
transform: rotateZ(150deg);
|
||||||
|
}
|
||||||
|
&.total-line3{
|
||||||
|
top: 21.5%;
|
||||||
|
left: 21%;
|
||||||
|
transform: rotateZ(-90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@for $i from 1 through 3 {
|
||||||
|
.percent-#{$i*25}{
|
||||||
|
width: $i * 25%;
|
||||||
|
height: $i * 25%;
|
||||||
|
clip-path: v-bind(styleFour);
|
||||||
|
background-color: rgba(255, 255, 255, .3);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: (4 - $i) * 2;
|
||||||
|
&::before{
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 10rpx);
|
||||||
|
height: calc(100% - 10rpx);
|
||||||
|
background-color: #87abfe;
|
||||||
|
clip-path: v-bind(styleFour);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.data-box{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
clip-path: v-bind(styleFourResult);
|
||||||
|
position: absolute;
|
||||||
|
background-color: #fff;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 12;
|
||||||
|
overflow: hidden;
|
||||||
|
.line-box{
|
||||||
|
display: block;
|
||||||
|
width: calc(100% - 24rpx);
|
||||||
|
height: calc(100% - 24rpx);
|
||||||
|
// background-color: #6691fe;
|
||||||
|
background-color: #6691fe;
|
||||||
|
clip-path: v-bind(styleFourResult);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 through 3 {
|
||||||
|
.percent-#{$i*25}{
|
||||||
|
background-color: rgba(255, 255, 255, .1);
|
||||||
|
z-index: (4 - $i) * 2 + 10;
|
||||||
|
&::before{
|
||||||
|
background-color: #6692ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title{
|
||||||
|
padding: 24rpx;
|
||||||
|
line-height: 46rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #fff;
|
||||||
|
.title-icon{
|
||||||
|
width: 27rpx;
|
||||||
|
height: 27rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.total-score{
|
||||||
|
position: absolute;
|
||||||
|
right: 30rpx;
|
||||||
|
top:30rpx;
|
||||||
|
width: 100rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 70rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-shadow: 0 4px 12px rgba(0, 0, 0, .33);
|
||||||
|
.score-num{
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 60rpx;
|
||||||
|
line-height: 70rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.total-score-icon{
|
||||||
|
width: 100%;
|
||||||
|
height: 16rpx;
|
||||||
|
position: absolute;
|
||||||
|
bottom:0;
|
||||||
|
left:0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.score-item{
|
||||||
|
position: absolute;
|
||||||
|
color: #fff;
|
||||||
|
width: 120rpx;
|
||||||
|
text-align: center;
|
||||||
|
.score-item-name{
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
.score-item-score{
|
||||||
|
font-size: 38rpx;
|
||||||
|
line-height: 46rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
&.score-item1{
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -60rpx;
|
||||||
|
bottom: 50%;
|
||||||
|
transform: translateY(v-bind(scoreMarginAbsY));
|
||||||
|
}
|
||||||
|
&.score-item2{
|
||||||
|
left: calc( 50% + v-bind(scoreMarginX));
|
||||||
|
bottom: calc(v-bind(scoreMarginPercentY) - 30rpx);
|
||||||
|
}
|
||||||
|
&.score-item3{
|
||||||
|
right: calc( 50% + v-bind(scoreMarginX));
|
||||||
|
bottom: calc(v-bind(scoreMarginPercentY) - 30rpx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,39 +2,54 @@
|
|||||||
<view class="ai-answer" v-if="type === 0">
|
<view class="ai-answer" v-if="type === 0">
|
||||||
<view class="user-info">
|
<view class="user-info">
|
||||||
<view class="avatar-box">
|
<view class="avatar-box">
|
||||||
<ImagePreview class="img-box" :imgId="''"></ImagePreview>
|
<ImagePreview class="img-box" :imgId="dataInfo?.userInfo?.imgAddr"></ImagePreview>
|
||||||
</view>
|
</view>
|
||||||
<view class="ai-name">
|
<view class="ai-name">
|
||||||
梁安娜
|
{{ dataInfo?.userInfo?.teacherName }}
|
||||||
<view class="ai-name-desc">AI课程教练</view>
|
<view class="ai-name-desc">{{dataInfo?.userInfo?.teacherDesc}}</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="talking-info">
|
<view class="talking-info">
|
||||||
<view class="talking-gif is-play"></view>
|
<view :class="['talking-gif', activeVoiceId === dataInfo.id?'is-play':'']"></view>
|
||||||
<view class="talking-cont">
|
<view class="talking-cont">
|
||||||
|
<view class="talking-text">
|
||||||
|
{{ dataInfo.pgrphContent }}
|
||||||
|
</view>
|
||||||
|
<ImagePreview class="cont-img-box" v-if="dataInfo.imageAddrs.length>0" :imgId="dataInfo.imageAddrs[0]"></ImagePreview>
|
||||||
|
<VideoPreview class="cont-img-box" v-if="dataInfo.vidoAddrs.length>0" :imgId="dataInfo.vidoAddrs[0]"></VideoPreview>
|
||||||
|
<ChartFour/>
|
||||||
|
<ChartThree/>
|
||||||
|
<!-- <ChartFour/>
|
||||||
|
<ChartThree/> -->
|
||||||
|
</view>
|
||||||
|
<!-- <view class="talking-cont">
|
||||||
<view class="talking-text">
|
<view class="talking-text">
|
||||||
阿列克山大是快乐的你艾德卡省的mask安东拉森扣篮大赛了快递那里是啊克里斯蒂娜老师的阿萨是看,
|
阿列克山大是快乐的你艾德卡省的mask安东拉森扣篮大赛了快递那里是啊克里斯蒂娜老师的阿萨是看,
|
||||||
</view>
|
</view>
|
||||||
<!-- <ImagePreview class="cont-img-box" :imgId="''"></ImagePreview>
|
</view> -->
|
||||||
<VideoPreview class="cont-img-box" :imgId="''"></VideoPreview> -->
|
|
||||||
</view>
|
|
||||||
<view class="talking-cont">
|
|
||||||
<view class="talking-text">
|
|
||||||
阿列克山大是快乐的你艾德卡省的mask安东拉森扣篮大赛了快递那里是啊克里斯蒂娜老师的阿萨是看,
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="btns-cont">
|
<view class="btns-cont">
|
||||||
<view class="replay-btn">
|
<view class="replay-btn" @click="restartPlay">
|
||||||
<image class="icon" src="@/static/images/course/replay.png" style="width: 100%;height: 100%;"></image>
|
<image class="icon" src="@/static/images/course/replay.png" style="width: 100%;height: 100%;"></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="play-btn">
|
<view class="play-btn">
|
||||||
<image class="icon" v-if="false" src="@/static/images/course/play.png" style="width: 100%;height: 100%;"></image>
|
<image
|
||||||
<image class="icon" src="@/static/images/course/pause.png" style="width: 100%;height: 100%;"></image>
|
class="icon"
|
||||||
|
src="@/static/images/course/play.png"
|
||||||
|
style="width: 100%;height: 100%;"
|
||||||
|
@click="playVoice"
|
||||||
|
v-show="props.activeVoiceId !== dataInfo.id"
|
||||||
|
></image>
|
||||||
|
<image
|
||||||
|
class="icon"
|
||||||
|
v-show="props.activeVoiceId === dataInfo.id"
|
||||||
|
src="@/static/images/course/pause.png"
|
||||||
|
style="width: 100%;height: 100%;"
|
||||||
|
@click="pauseVoice"
|
||||||
|
></image>
|
||||||
</view>
|
</view>
|
||||||
<view class="next-learn-btn">继续学<image class="icon icon-iamge" src="@/static/images/course/goon.png" ></image></view>
|
<!-- <view class="next-learn-btn">继续学<image class="icon icon-iamge" src="@/static/images/course/goon.png" ></image></view>
|
||||||
<!-- <view class="replay-learn-btn">重新学<image class="icon icon-iamge" src="@/static/images/course/restart.png" ></image></view> -->
|
<view class="replay-study-btn"><image class="icon icon-iamge" src="@/static/images/course/restudy.png" ></image></view> -->
|
||||||
<view class="replay-study-btn"><image class="icon icon-iamge" src="@/static/images/course/restudy.png" ></image></view>
|
<view class="next-btn" @click="goOnGetQues" v-if="showNextBtn">继续
|
||||||
<view class="next-btn">继续
|
|
||||||
<image class="icon icon-iamge" src="@/static/images/course/goon.png" ></image></view>
|
<image class="icon icon-iamge" src="@/static/images/course/goon.png" ></image></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -42,10 +57,11 @@
|
|||||||
<view class="user-answer" v-else-if="type === 1">
|
<view class="user-answer" v-else-if="type === 1">
|
||||||
<view class="user-info">
|
<view class="user-info">
|
||||||
<view class="avatar-box">
|
<view class="avatar-box">
|
||||||
<ImagePreview class="img-box" :imgId="''"></ImagePreview>
|
<!-- <ImagePreview class="img-box" :imgId="''"></ImagePreview> -->
|
||||||
|
<img class="img-box" :src="userInfo.imageAddr" alt="" fit="contain" style="width: 100%; height: 100%;"/>
|
||||||
</view>
|
</view>
|
||||||
<view class="ai-name">
|
<view class="ai-name">
|
||||||
玛卡巴卡
|
{{ userInfo.userName }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -53,7 +69,7 @@
|
|||||||
<view class="talking-gif"></view>
|
<view class="talking-gif"></view>
|
||||||
<view class="talking-cont">
|
<view class="talking-cont">
|
||||||
<view class="talking-text">
|
<view class="talking-text">
|
||||||
阿列克山大是快乐的你艾德卡省的mask安东拉森扣篮大赛了快递那里是啊克里斯蒂娜老师的阿萨是看,
|
{{ dataInfo.talkingContent }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="btns-cont">
|
<view class="btns-cont">
|
||||||
@@ -67,18 +83,112 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="user-answer" v-else-if="type===2">
|
||||||
|
<view class="user-info">
|
||||||
|
<view class="avatar-box">
|
||||||
|
<!-- <ImagePreview class="img-box" :imgId="''"></ImagePreview> -->
|
||||||
|
<img class="img-box" :src="userInfo.imageAddr" alt="" fit="contain" style="width: 100%; height: 100%;"/>
|
||||||
|
</view>
|
||||||
|
<view class="ai-name">
|
||||||
|
{{ userInfo.userName }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="talking-info-less">
|
||||||
|
<view class="talking-text">
|
||||||
|
{{ dataInfo.talkingContent }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
<view v-else></view>
|
<view v-else></view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ImagePreview from '@/components/image-preview/image-preview.vue'
|
import { watch, unref, ref, toRefs, computed } from 'vue'
|
||||||
import VideoPreview from '@/components/image-preview/video-preview.vue'
|
import ImagePreview from '@/components/image-preview/image-preview.vue'
|
||||||
|
import VideoPreview from '@/components/image-preview/video-preview.vue'
|
||||||
|
import { downloadVoiceFile } from "@/api/common.js"
|
||||||
|
import { studyParagraphOverApi } from "@/api/study.js"
|
||||||
|
import ChartFour from "./chartFour.vue"
|
||||||
|
import ChartThree from "./chartThree.vue"
|
||||||
|
import { getUserInfo } from '@/common/common';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
type:{
|
type:{
|
||||||
default: 0,// 1: userAnswer 0: AIAnswer
|
default: 0,// 1: userAnswer 0: AIAnswer
|
||||||
type:[Number,String]
|
type:[Number,String]
|
||||||
|
},
|
||||||
|
dataInfo: {
|
||||||
|
default: () => ({}),
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
activeVoiceId:{
|
||||||
|
default:'',
|
||||||
|
type: String
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const { type, dataInfo, activeVoiceId } = toRefs(props)
|
||||||
|
const emit = defineEmits(['changePlay', 'nextQuestion'])
|
||||||
|
const talkVoiceObj = uni.createInnerAudioContext()
|
||||||
|
const userInfo = computed(() => getUserInfo())
|
||||||
|
watch(() => props.activeVoiceId,(val) => {
|
||||||
|
if(val && (val !== unref(dataInfo).id)){
|
||||||
|
talkVoiceObj?.stop()
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
})
|
||||||
|
|
||||||
|
talkVoiceObj.onEnded(()=>{
|
||||||
|
// talkVoiceObj.src = ''
|
||||||
|
emit('changePlay',unref(dataInfo)?.id)
|
||||||
|
})
|
||||||
|
const pauseVoice = () => {
|
||||||
|
if(!talkVoiceObj) return
|
||||||
|
talkVoiceObj.pause()
|
||||||
|
emit('changePlay',unref(dataInfo)?.id)
|
||||||
|
}
|
||||||
|
const restartPlay = () => {
|
||||||
|
console.log(userInfo)
|
||||||
|
if(talkVoiceObj.src){
|
||||||
|
talkVoiceObj.seek(0)
|
||||||
|
talkVoiceObj.play()
|
||||||
|
}else{
|
||||||
|
playVoice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const playVoice = (readType = 'pgth') =>{
|
||||||
|
// readType 阅读内容类型pgrh段落/qns问题
|
||||||
|
emit('changePlay', unref(dataInfo)?.id)
|
||||||
|
if(talkVoiceObj.src){
|
||||||
|
talkVoiceObj.play()
|
||||||
|
}else{
|
||||||
|
let _params = {
|
||||||
|
crsId: unref(dataInfo)?.crsId || '',
|
||||||
|
pgrphId: unref(dataInfo)?.pgrphId || '',
|
||||||
|
qnsId: unref(dataInfo)?.qnsId || '',
|
||||||
|
readType: readType,
|
||||||
|
teachNo: unref(dataInfo)?.userInfo?.teacherNo, // 必填
|
||||||
|
content: unref(dataInfo)?.pgrphContent,
|
||||||
|
}
|
||||||
|
let _url = '/trastudy/traStdyInfo/readContent?' + Object.keys(_params).map(t=>{
|
||||||
|
return encodeURIComponent(t) + '=' + encodeURIComponent(_params[t])
|
||||||
|
}).join('&')
|
||||||
|
downloadVoiceFile(_url).then(res=>{
|
||||||
|
talkVoiceObj.src = res.tempFilePath
|
||||||
|
talkVoiceObj.play()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const showNextBtn = ref(true)
|
||||||
|
const goOnGetQues = () => {
|
||||||
|
studyParagraphOverApi({
|
||||||
|
pgrphId:unref(dataInfo)?.pgrphId
|
||||||
|
}).then(res=>{
|
||||||
|
showNextBtn.value = false
|
||||||
|
emit('nextQuestion', unref(dataInfo))
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -239,7 +349,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.user-answer{
|
.user-answer{
|
||||||
|
overflow: hidden;
|
||||||
.user-info{
|
.user-info{
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.avatar-box{
|
.avatar-box{
|
||||||
@@ -265,6 +375,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.talking-info-less{
|
||||||
|
padding: 20rpx;
|
||||||
|
float: right;
|
||||||
|
border-radius: 15rpx;
|
||||||
|
background-color: #06f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
.talking-info{
|
.talking-info{
|
||||||
padding: 20rpx;
|
padding: 20rpx;
|
||||||
border-radius: 15rpx;
|
border-radius: 15rpx;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
<uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/>
|
<uv-icon class="arrow-icon" name="arrow-left" @click="common.navigateBack()"/>
|
||||||
语言选择,开启学习之旅
|
语言选择,开启学习之旅
|
||||||
</view>
|
</view>
|
||||||
<!-- <video v-if="videoUrlShow" :src="videoUrlShow" controls></video> -->
|
|
||||||
<view class="voice-list">
|
<view class="voice-list">
|
||||||
<uv-radio-group v-model="choiceTeacher">
|
<uv-radio-group v-model="choiceTeacher">
|
||||||
<view class="voice-item" v-for="item in teacherList" :key="item.teacherNo" @click="choiceTeacher = item.teacherNo">
|
<view class="voice-item" v-for="item in teacherList" :key="item.teacherNo" @click="choiceTeacher = item.teacherNo">
|
||||||
@@ -18,12 +17,11 @@
|
|||||||
<view class="right-name">{{item.teacherName}}</view>
|
<view class="right-name">{{item.teacherName}}</view>
|
||||||
<view class="right-desc">描述:{{item.voiceDesc || '--'}}</view>
|
<view class="right-desc">描述:{{item.voiceDesc || '--'}}</view>
|
||||||
<view class="audio-box">
|
<view class="audio-box">
|
||||||
<view class="talking-gif"></view>
|
<view :class="['talking-gif', activeVoiceTeachNo === item.teacherNo ? 'is-play':'']"></view>
|
||||||
<view class="play-btn">
|
<view class="play-btn" @click="playTeacherVoice(item)">
|
||||||
<image class="icon" v-if="false" src="@/static/images/course/play.png" style="width: 100%;height: 100%;"></image>
|
<image class="icon" v-if="activeVoiceTeachNo !== item.teacherNo" src="@/static/images/course/play-blue.png" style="width: 100%;height: 100%;"></image>
|
||||||
<image class="icon" src="@/static/images/course/pause-focus.png" style="width: 100%;height: 100%;"></image>
|
<image class="icon" v-else src="@/static/images/course/stop-blue.png" style="width: 100%;height: 100%;"></image>
|
||||||
</view>
|
</view>
|
||||||
<!-- <view class="talking-time">00:26</view> -->
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<uv-radio :name="item.teacherNo" class="radio-box" @click.stop/>
|
<uv-radio :name="item.teacherNo" class="radio-box" @click.stop/>
|
||||||
@@ -31,7 +29,6 @@
|
|||||||
</uv-radio-group>
|
</uv-radio-group>
|
||||||
<view class="start-btns" @click="nextStep">
|
<view class="start-btns" @click="nextStep">
|
||||||
开启学习之旅
|
开启学习之旅
|
||||||
<!-- <view class="touch-btn">开启学习之旅</view> -->
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -41,8 +38,19 @@
|
|||||||
课程学习
|
课程学习
|
||||||
</view>
|
</view>
|
||||||
<view class="talking-list">
|
<view class="talking-list">
|
||||||
<view class="study-num">1/20</view>
|
<view class="study-num">
|
||||||
<TalkingItem class="talking-item" v-for="item in 8" :type="item%2"></TalkingItem>
|
<view class="color-blue">1</view> / 20
|
||||||
|
</view>
|
||||||
|
<TalkingItem
|
||||||
|
class="talking-item"
|
||||||
|
v-for="item in talkingList"
|
||||||
|
:type="item.type"
|
||||||
|
:dataInfo="item"
|
||||||
|
:activeVoiceId="activeVoiceTalkingItemId"
|
||||||
|
@changePlay="changePlayItemId"
|
||||||
|
@nextQuestion="nextQuestionSuccess"
|
||||||
|
></TalkingItem>
|
||||||
|
<!-- <video v-if="videoUrlShow" :src="videoUrlShow" controls></video> -->
|
||||||
</view>
|
</view>
|
||||||
<view class="talk-btns">
|
<view class="talk-btns">
|
||||||
<view
|
<view
|
||||||
@@ -50,7 +58,9 @@
|
|||||||
@touchstart="showOverlayTalking"
|
@touchstart="showOverlayTalking"
|
||||||
@touchend="hideOverlayTalking"
|
@touchend="hideOverlayTalking"
|
||||||
@touchmove="handleMove"
|
@touchmove="handleMove"
|
||||||
>按住说话</view>
|
>按住说话
|
||||||
|
<image class="talk-btn-icon" src="/src/static/images/course/jianpan.png" mode=""></image>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<uv-overlay :show="showTalkingModel" opacity=".9" @click="showTalkingModel = false">
|
<uv-overlay :show="showTalkingModel" opacity=".9" @click="showTalkingModel = false">
|
||||||
@@ -66,9 +76,6 @@
|
|||||||
<image class="btn-box" v-if="!isOut" src="/src/static/images/course/close.png" mode=""></image>
|
<image class="btn-box" v-if="!isOut" src="/src/static/images/course/close.png" mode=""></image>
|
||||||
<image class="btn-box" v-else src="/src/static/images/course/close-active.png" mode=""></image>
|
<image class="btn-box" v-else src="/src/static/images/course/close-active.png" mode=""></image>
|
||||||
</view>
|
</view>
|
||||||
<!-- <view class="translate-btn">转文字 发送
|
|
||||||
<image class="btn-box" src="/src/static/images/course/to-text.png" mode=""></image>
|
|
||||||
</view> -->
|
|
||||||
</view>
|
</view>
|
||||||
<!-- <view class="talk-dialog">
|
<!-- <view class="talk-dialog">
|
||||||
<view class="voice-gif"></view>
|
<view class="voice-gif"></view>
|
||||||
@@ -81,10 +88,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, ref, computed, unref, onMounted, onUnmounted } from 'vue';
|
import { reactive, ref, computed, unref, onMounted, onUnmounted } from 'vue';
|
||||||
import { onLoad,onShow,onHide } from '@dcloudio/uni-app';
|
import { onLoad,onShow,onHide } from '@dcloudio/uni-app';
|
||||||
import { previewFile, downloadFile } from "@/api/common.js"
|
import { previewFile, downloadFile, downloadVoiceFile, uploadVoiceFile } from "@/api/common.js"
|
||||||
import { getToken } from '@/common/common.js'
|
import { getToken } from '@/common/common.js'
|
||||||
import { get_base_url } from '@/api/request'
|
import { get_base_url } from '@/api/request'
|
||||||
import { getCourseStudyInfoApi } from "@/api/study.js"
|
import { getCourseStudyInfoApi, queryTraStdyBatchWaitParagraphInfoApi,audioTranscriptionsApi } from "@/api/study.js"
|
||||||
import common from '@/common/common';
|
import common from '@/common/common';
|
||||||
import TalkingItem from '@/pages/course/components/talkingItem.vue'
|
import TalkingItem from '@/pages/course/components/talkingItem.vue'
|
||||||
import ImagePreview from '@/components/image-preview/image-preview.vue'
|
import ImagePreview from '@/components/image-preview/image-preview.vue'
|
||||||
@@ -94,102 +101,174 @@ const step = ref(0)
|
|||||||
const teacherList = ref([])
|
const teacherList = ref([])
|
||||||
const choiceTeacher = ref('')
|
const choiceTeacher = ref('')
|
||||||
const videoUrlShow = ref('')
|
const videoUrlShow = ref('')
|
||||||
|
const talkingList = ref([])
|
||||||
|
const activeVoiceTalkingItemId = ref('')
|
||||||
|
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
try{
|
try{
|
||||||
const { body } = await getCourseStudyInfoApi({crsId: crsId.value})
|
const { body } = await getCourseStudyInfoApi({crsId: crsId.value})
|
||||||
if(body.stdyProgressFlag === 'N'){
|
if(body.stdyProgressFlag === 'N'){
|
||||||
step.value = 1
|
step.value = 1
|
||||||
teacherList.value = body.teacheList
|
teacherList.value = body.teacheList.map(t=>({
|
||||||
|
...t,
|
||||||
|
voicePath: '',
|
||||||
|
voiceObj: null
|
||||||
|
}))
|
||||||
|
initsocketTask(1)
|
||||||
}else{
|
}else{
|
||||||
step.value = 2
|
step.value = 2
|
||||||
choiceTeacher.value = body.choiceTeacher
|
choiceTeacher.value = body.choiceTeacher
|
||||||
initWS(2)
|
initsocketTask(2)
|
||||||
}
|
}
|
||||||
}catch{
|
}catch{
|
||||||
|
|
||||||
}
|
}
|
||||||
// downloadFile('S3F0250181220722025062409312400000331622').then(res=>{
|
|
||||||
// console.log(res)
|
|
||||||
// audioObj.value = uni.createInnerAudioContext()
|
|
||||||
// audioObj.value.src = res.tempFilePath
|
|
||||||
// })
|
|
||||||
// downloadFile('S3F0250640161332025061618052000000267276').then(res=>{
|
// downloadFile('S3F0250640161332025061618052000000267276').then(res=>{
|
||||||
// videoUrlShow.value = res.tempFilePath
|
// videoUrlShow.value = res.tempFilePath
|
||||||
// })
|
// })
|
||||||
};
|
};
|
||||||
var WS = null
|
|
||||||
|
// 关闭播放状态
|
||||||
|
const changePlayItemId = (id) => {
|
||||||
|
if(activeVoiceTalkingItemId.value === id){
|
||||||
|
activeVoiceTalkingItemId.value = ''
|
||||||
|
}else{
|
||||||
|
activeVoiceTalkingItemId.value = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const choiceTeacherInfo = computed(()=>{
|
||||||
|
return teacherList.value.find(t=> t.teacherNo === choiceTeacher.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeVoiceTeachNo = ref('')
|
||||||
|
const teacherVoiceObj = uni.createInnerAudioContext()
|
||||||
|
teacherVoiceObj.onEnded(()=>{
|
||||||
|
activeVoiceTeachNo.value = ''
|
||||||
|
teacherVoiceObj.src = ''
|
||||||
|
})
|
||||||
|
const playTeacherVoice = (item) =>{
|
||||||
|
teacherVoiceObj.stop()
|
||||||
|
teacherVoiceObj.src = ''
|
||||||
|
let _isStop = item.teacherNo === activeVoiceTeachNo.value
|
||||||
|
activeVoiceTeachNo.value = _isStop ? '' : item.teacherNo
|
||||||
|
if(_isStop) return
|
||||||
|
if(item.voicePath){
|
||||||
|
teacherVoiceObj.src = item.voicePath
|
||||||
|
teacherVoiceObj.play()
|
||||||
|
}else{
|
||||||
|
downloadVoiceFile('/trastudy/traStdyInfo/trialListening?teachNo='+item.teacherNo).then(res=>{
|
||||||
|
item.voicePath = res.tempFilePath
|
||||||
|
teacherVoiceObj.src = item.voicePath
|
||||||
|
teacherVoiceObj.play()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var socketTask = null
|
||||||
const nextStep = () => {
|
const nextStep = () => {
|
||||||
if(choiceTeacher.value){
|
if(choiceTeacher.value){
|
||||||
initWS(1)
|
teacherVoiceObj.destroy()
|
||||||
|
console.log(111)
|
||||||
|
if( socketTask && socketTask.readyState === 1){
|
||||||
|
sendMessage({
|
||||||
|
"commond" : 'open',
|
||||||
|
"body" : {
|
||||||
|
crsId: crsId.value,
|
||||||
|
teacherNo: choiceTeacher.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
step.value = 2
|
||||||
|
getGraphInfoUnStudy()
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '请选择一位教师!',
|
title: '请选择一位教师!',
|
||||||
icon: "none",
|
icon: "none",
|
||||||
duration
|
duration:1000
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const reconnectTimer = null // 重连timer
|
const getGraphInfoUnStudy = () => {
|
||||||
const isManualClose = false // 是否手动关闭
|
queryTraStdyBatchWaitParagraphInfoApi({crsId: crsId.value}).then(res=>{
|
||||||
|
console.log(res)
|
||||||
const initWS = ($step) => {
|
let _id = new Date().getTime() + 'id'
|
||||||
WS = uni.connectSocket({
|
talkingList.value.push({
|
||||||
url: get_base_url() + '/trastudy/trasoket/open?summary=' + getToken(),
|
...res.body,
|
||||||
complete: function(cv){
|
type: 0,
|
||||||
console.log(cv)
|
userInfo: {...choiceTeacherInfo.value},
|
||||||
}
|
id: _id
|
||||||
|
})
|
||||||
})
|
})
|
||||||
WS.onMessage = (event) => {
|
}
|
||||||
console.log(event,111)
|
// 继续 执行方法
|
||||||
}
|
const nextQuestionSuccess = (info) => {
|
||||||
WS.onSocketMessage = (event) => {
|
let _id = new Date().getTime() + 'id'
|
||||||
console.log(event,222)
|
talkingList.value.push({
|
||||||
}
|
crsId: info.crsId,
|
||||||
WS.onOpen = () =>{
|
pgrphId: info.pgrphId,
|
||||||
console.log('建立链接')
|
talkingContent: '继续',
|
||||||
}
|
type: 2,
|
||||||
WS.onError = (event) => {
|
id: _id
|
||||||
console.log('error',event)
|
})
|
||||||
}
|
getGraphInfoUnStudy()
|
||||||
// WS.onclose = () => {
|
}
|
||||||
|
|
||||||
|
const reconnectTimer = null // 重连timer
|
||||||
|
const isManualClose = ref(false) // 是否手动关闭
|
||||||
|
|
||||||
|
const initsocketTask = ($step) => {
|
||||||
|
socketTask = uni.connectSocket({
|
||||||
|
url: get_base_url() + '/trastudy/trasoket/open?summary=' + getToken(),
|
||||||
|
complete: function(){}
|
||||||
|
})
|
||||||
|
uni.onSocketMessage((res)=>{
|
||||||
|
let _data = JSON.parse(res.data)
|
||||||
|
console.log(_data)
|
||||||
|
})
|
||||||
|
uni.onSocketOpen((e)=>{
|
||||||
|
console.log('链接成功!')
|
||||||
|
})
|
||||||
|
uni.onSocketError((e)=>{
|
||||||
|
console.log(e,111)
|
||||||
|
})
|
||||||
|
uni.onSocketClose((e)=>{
|
||||||
|
// 重连
|
||||||
|
reconnect()
|
||||||
|
})
|
||||||
|
// socketTask.onMessage = (event) => {
|
||||||
|
// console.log(event,111)
|
||||||
|
// }
|
||||||
|
// socketTask.onOpen = () =>{
|
||||||
|
// console.log('建立链接')
|
||||||
|
// }
|
||||||
|
// socketTask.onError = (event) => {
|
||||||
|
// console.log('error',event)
|
||||||
|
// }
|
||||||
|
// socketTask.onclose = () => {
|
||||||
// // 重连
|
// // 重连
|
||||||
// // initWS()
|
// // initsocketTask()
|
||||||
// }
|
// }
|
||||||
setTimeout(()=>{
|
|
||||||
console.log(WS , WS.readyState)
|
|
||||||
if( WS && WS.readyState === 1){
|
|
||||||
let _params = $step === 1 ?{
|
|
||||||
"commond" : 'open',
|
|
||||||
"body" : {
|
|
||||||
crsId: crsId.value,
|
|
||||||
teacherNo: choiceTeacher.value
|
|
||||||
}
|
|
||||||
}:{}
|
|
||||||
sendMessage(_params)
|
|
||||||
step.value = 2
|
|
||||||
}
|
|
||||||
},100)
|
|
||||||
}
|
}
|
||||||
const reconnect = ()=> {
|
const reconnect = ()=> {
|
||||||
if(reconnectTimer || isManualClose) return
|
if(reconnectTimer || isManualClose.value) return
|
||||||
reconnectTimer = setTimeout(()=>{
|
reconnectTimer = setTimeout(()=>{
|
||||||
initWS(2)
|
initsocketTask(2)
|
||||||
},3000)
|
},3000)
|
||||||
}
|
}
|
||||||
const sendMessage = (data) => {
|
const sendMessage = (data) => {
|
||||||
WS.send({
|
socketTask.send({
|
||||||
data: JSON.stringify(data)
|
data: JSON.stringify(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const closeWebSocket = () => {
|
const closeWebSocket = () => {
|
||||||
isManualClose = true
|
isManualClose.value = true
|
||||||
if(WS){
|
if(socketTask){
|
||||||
WS.close()
|
// socketTask.close()
|
||||||
WS = null
|
socketTask = null
|
||||||
|
uni.closeSocket({})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onLoad((params) => {
|
onLoad((params) => {
|
||||||
crsId.value = params.crsId||'';
|
crsId.value = params.crsId||'';
|
||||||
crsNum.value = params.crsNum||'';
|
crsNum.value = params.crsNum||'';
|
||||||
@@ -207,32 +286,29 @@ const initRecord = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
const startRecord = () => {
|
const startRecord = () => {
|
||||||
unref(recordObj).start()
|
unref(recordObj).start({format:'mp3'})
|
||||||
}
|
}
|
||||||
const stopRecord = () => {
|
const stopRecord = () => {
|
||||||
unref(recordObj).stop()
|
unref(recordObj).stop()
|
||||||
}
|
}
|
||||||
// onMounted(()=>{
|
|
||||||
// initRecord()
|
|
||||||
// })
|
|
||||||
// #endif
|
// #endif
|
||||||
//#ifndef APP-PLUS
|
//#ifndef APP-PLUS
|
||||||
const recordObj = {}
|
const recordObj = {}
|
||||||
const initRecord = () => {}
|
const initRecord = () => {}
|
||||||
// #endif
|
// #endif
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
getData();
|
// getData();
|
||||||
initRecord?.()
|
initRecord?.()
|
||||||
})
|
})
|
||||||
onShow(()=>{
|
onShow(()=>{
|
||||||
getData();
|
getData();
|
||||||
})
|
})
|
||||||
onHide(()=>{
|
onHide(()=>{
|
||||||
closeWebSocket()
|
|
||||||
})
|
|
||||||
onUnmounted(()=>{
|
|
||||||
closeWebSocket()
|
closeWebSocket()
|
||||||
})
|
})
|
||||||
|
// onUnmounted(()=>{
|
||||||
|
// closeWebSocket()
|
||||||
|
// })
|
||||||
const playRecord = () => {
|
const playRecord = () => {
|
||||||
audioObj.src = audioPath.value
|
audioObj.src = audioPath.value
|
||||||
unref(audioObj).play()
|
unref(audioObj).play()
|
||||||
@@ -255,7 +331,9 @@ const showOverlayTalking = (obj) => {
|
|||||||
touchX.value = obj.changedTouches[0].pageX
|
touchX.value = obj.changedTouches[0].pageX
|
||||||
touchY.value = obj.changedTouches[0].pageY
|
touchY.value = obj.changedTouches[0].pageY
|
||||||
baseY.value = obj.currentTarget.offsetTop
|
baseY.value = obj.currentTarget.offsetTop
|
||||||
|
//#ifdef APP-PLUS
|
||||||
startRecord()
|
startRecord()
|
||||||
|
// #endif
|
||||||
}
|
}
|
||||||
const hideOverlayTalking = (obj) => {
|
const hideOverlayTalking = (obj) => {
|
||||||
showTalkingModel.value = false
|
showTalkingModel.value = false
|
||||||
@@ -264,6 +342,17 @@ const hideOverlayTalking = (obj) => {
|
|||||||
// #endif
|
// #endif
|
||||||
if(!isOut.value){
|
if(!isOut.value){
|
||||||
console.log('发送')
|
console.log('发送')
|
||||||
|
// let formData = new FormData()
|
||||||
|
// formData.append('voice',)
|
||||||
|
console.log(audioPath.value)
|
||||||
|
setTimeout(()=>{
|
||||||
|
|
||||||
|
uploadVoiceFile(audioPath.value,{}).then(res=>{
|
||||||
|
console.log(JSON.parse(res.data))
|
||||||
|
}).catch(err=>{
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
},1000)
|
||||||
}
|
}
|
||||||
// else{
|
// else{
|
||||||
// console.log('取消发送')
|
// console.log('取消发送')
|
||||||
@@ -333,6 +422,10 @@ const handleMove = (obj) => {
|
|||||||
line-height: 36rpx;
|
line-height: 36rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 15rpx;
|
margin-bottom: 15rpx;
|
||||||
|
.color-blue{
|
||||||
|
color: #06f;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.talking-item{
|
.talking-item{
|
||||||
margin-bottom: 46rpx;
|
margin-bottom: 46rpx;
|
||||||
@@ -349,6 +442,15 @@ const handleMove = (obj) => {
|
|||||||
line-height: 92rpx;
|
line-height: 92rpx;
|
||||||
color: #000;
|
color: #000;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
position: relative;
|
||||||
|
.talk-btn-icon{
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
position: absolute;
|
||||||
|
right: 40rpx;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -20rpx
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.voice-list{
|
.voice-list{
|
||||||
@@ -528,4 +630,5 @@ const handleMove = (obj) => {
|
|||||||
background-color: #06f;
|
background-color: #06f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
<<<<<<< HEAD
|
||||||
import {
|
import {
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
@@ -138,6 +139,31 @@
|
|||||||
userInfo.value = getUserInfo()
|
userInfo.value = getUserInfo()
|
||||||
console.log(userInfo.value);
|
console.log(userInfo.value);
|
||||||
});
|
});
|
||||||
|
=======
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { onLoad } from '@dcloudio/uni-app';
|
||||||
|
import common from '@/common/common';
|
||||||
|
const index = ref(0);
|
||||||
|
const form = ref({
|
||||||
|
gender: 'Y'
|
||||||
|
});
|
||||||
|
const sexOptions = [
|
||||||
|
{ value: 'Y', label: '男' },
|
||||||
|
{ value: 'M', label: '女' }
|
||||||
|
];
|
||||||
|
const cellCLick = (value) => {
|
||||||
|
sexOptions.length = 0;
|
||||||
|
console.log(sexOptions,'sisisis');
|
||||||
|
};
|
||||||
|
const sexChange = (e) => {
|
||||||
|
console.log(e);
|
||||||
|
console.log(sexOptions[e.detail.value].value, 'sexOptions[e.detail.value].value');
|
||||||
|
form.value.gender = sexOptions[e.detail.value].value;
|
||||||
|
|
||||||
|
console.log(form.value.gender, 'iiii');
|
||||||
|
};
|
||||||
|
onLoad((params) => {});
|
||||||
|
>>>>>>> cea58939be69e08335a0aaf3f8fef73c5fdb914a
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<template>
|
||||||
|
<view class="main_div max_page">
|
||||||
|
<view class="swiper-box">
|
||||||
|
<uv-swiper
|
||||||
|
height="100%"
|
||||||
|
indicator-active-color="#0066ff"
|
||||||
|
indicator-inactive-color="#D7E1F0"
|
||||||
|
:list="imgList"
|
||||||
|
indicator
|
||||||
|
indicatorMode="dot"
|
||||||
|
imgMode="scaleToFill"
|
||||||
|
:autoplay="false"
|
||||||
|
:current="indexNow"
|
||||||
|
@change="swiperChange"
|
||||||
|
@transition="swiperTransition"
|
||||||
|
@animationfinish="animationfinish"
|
||||||
|
></uv-swiper>
|
||||||
|
</view>
|
||||||
|
<view class="text-div">
|
||||||
|
<view class="title font_pf">{{ textList[indexNow].title }}</view>
|
||||||
|
<view class="tips font_pf">{{ textList[indexNow].tips }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import common from '@/common/common.js'
|
||||||
|
const imgList = ['/static/images/welcome/01.png', '/static/images/welcome/03.png', '/static/images/welcome/02.png'];
|
||||||
|
const textList = [
|
||||||
|
{ title: '学霸已就位', tips: '遇见AI学习搭子,难题随时有解!' },
|
||||||
|
{ title: '碎片时间逆袭', tips: '碎片时间全利用,定制课程随身学,成长不停歇!' },
|
||||||
|
{ title: '闯关式学习超上头', tips: '闯关升级,模拟实战,行业精英非你莫属!' }
|
||||||
|
];
|
||||||
|
const indexNow = ref(0);
|
||||||
|
const indexNowNow = ref(0)
|
||||||
|
const swiperChange = (index) => {
|
||||||
|
indexNow.value = index.current;
|
||||||
|
};
|
||||||
|
const animationfinish = () => {
|
||||||
|
indexNowNow.value = indexNow.value
|
||||||
|
}
|
||||||
|
const swiperTransition = (e) => {
|
||||||
|
if (e.dx > 30 && indexNowNow.value == 2) {
|
||||||
|
common.navigateBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.main_div {
|
||||||
|
background-color: #ffffff;
|
||||||
|
height: 100vh;
|
||||||
|
// background-color: red;
|
||||||
|
}
|
||||||
|
.swiper-box {
|
||||||
|
width: 100%;
|
||||||
|
height: 80%;
|
||||||
|
:deep(.uv-swiper) {
|
||||||
|
overflow: unset;
|
||||||
|
}
|
||||||
|
:deep(.uv-swiper__indicator) {
|
||||||
|
bottom: -40rpx;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
:deep(.uv-swiper-indicator__wrapper__dot) {
|
||||||
|
width: 15rpx;
|
||||||
|
height: 15rpx;
|
||||||
|
}
|
||||||
|
:deep(.uv-swiper-indicator__wrapper__dot--active) {
|
||||||
|
width: 54rpx;
|
||||||
|
height: 15rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.text-div {
|
||||||
|
width: 100%;
|
||||||
|
height: 20%;
|
||||||
|
padding-top: 104rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
// justify-content: center;
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000000;
|
||||||
|
font-size: 46rpx;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
.tips {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,41 +1,50 @@
|
|||||||
{
|
{
|
||||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||||
{
|
{
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "首页",
|
"navigationBarTitleText": "首页",
|
||||||
"app-plus": {
|
"app-plus": {
|
||||||
"titleNView": false
|
"titleNView": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/courseDetail/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "课程详情",
|
||||||
|
"app-plus": {
|
||||||
|
"titleNView": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/courseDetail/submit",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "提交评价",
|
||||||
|
"app-plus": {
|
||||||
|
"titleNView": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/setting/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "设置",
|
||||||
|
"app-plus": {
|
||||||
|
"titleNView": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/welcome/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "欢迎",
|
||||||
|
"app-plus": {
|
||||||
|
"titleNView": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/courseDetail/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "课程详情",
|
|
||||||
"app-plus": {
|
|
||||||
"titleNView": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/courseDetail/submit",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "提交评价",
|
|
||||||
"app-plus": {
|
|
||||||
"titleNView": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "pages/setting/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "设置",
|
|
||||||
"app-plus": {
|
|
||||||
"titleNView": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
"list": [{
|
"list": [{
|
||||||
|
|||||||
|
After Width: | Height: | Size: 468 B |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 864 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,11 @@
|
|||||||
|
## 1.0.4(2023-10-13)
|
||||||
|
1. 优化
|
||||||
|
## 1.0.3(2023-10-13)
|
||||||
|
1. unmounted兼容vue3
|
||||||
|
## 1.0.2(2023-05-27)
|
||||||
|
1. 不支持抖音小程序说明
|
||||||
|
## 1.0.1(2023-05-16)
|
||||||
|
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
|
||||||
|
2. 优化部分功能
|
||||||
|
## 1.0.0(2023-05-10)
|
||||||
|
uv-swipe-action 滑动单元格
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
/**
|
||||||
|
* 此为wxs模块,只支持APP-VUE,微信和QQ小程序以及H5平台
|
||||||
|
* wxs内部不支持es6语法,变量只能使用var定义,无法使用解构,箭头函数等特性
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 开始触摸
|
||||||
|
function touchstart(event, ownerInstance) {
|
||||||
|
// 触发事件的组件的ComponentDescriptor实例
|
||||||
|
var instance = event.instance
|
||||||
|
// wxs内的局部变量快照,此快照是属于整个组件的,在touchstart和touchmove事件中都能获取到相同的结果
|
||||||
|
var state = instance.getState()
|
||||||
|
if (state.disable) return
|
||||||
|
var touches = event.touches
|
||||||
|
// 如果进行的是多指触控,不允许进行操作
|
||||||
|
if (touches && touches.length > 1) return
|
||||||
|
// 标识当前为滑动中状态
|
||||||
|
state.moving = true
|
||||||
|
// 记录触摸开始点的坐标值
|
||||||
|
state.startX = touches[0].pageX
|
||||||
|
state.startY = touches[0].pageY
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触摸滑动
|
||||||
|
function touchmove(event, ownerInstance) {
|
||||||
|
// 触发事件的组件的ComponentDescriptor实例
|
||||||
|
var instance = event.instance
|
||||||
|
// wxs内的局部变量快照
|
||||||
|
var state = instance.getState()
|
||||||
|
if (state.disabled || !state.moving) return
|
||||||
|
|
||||||
|
var touches = event.touches
|
||||||
|
var pageX = touches[0].pageX
|
||||||
|
var pageY = touches[0].pageY
|
||||||
|
var moveX = pageX - state.startX
|
||||||
|
var moveY = pageY - state.startY
|
||||||
|
var buttonsWidth = state.buttonsWidth
|
||||||
|
|
||||||
|
// 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
|
||||||
|
if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
// 如果移动的X轴距离小于Y轴距离,也即终点位置与起点位置连线,与Y轴夹角小于45度时,认为是页面上下滑动,而不是左右滑动单元格
|
||||||
|
if (Math.abs(moveX) < Math.abs(moveY)) return
|
||||||
|
|
||||||
|
// 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
|
||||||
|
// 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,最好的办法就是
|
||||||
|
// 在超出后,设置为0
|
||||||
|
if (state.status === 'open') {
|
||||||
|
// 在开启状态下,向左滑动,需忽略
|
||||||
|
if (moveX < 0) moveX = 0
|
||||||
|
// 想要收起菜单,最大能移动的距离为按钮的总宽度
|
||||||
|
if (moveX > buttonsWidth) moveX = buttonsWidth
|
||||||
|
// 如果是已经打开了的状态,向左滑动时,移动收起菜单
|
||||||
|
moveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)
|
||||||
|
} else {
|
||||||
|
// 关闭状态下,右滑动需忽略
|
||||||
|
if (moveX > 0) moveX = 0
|
||||||
|
// 滑动的距离不允许超过所有按钮的总宽度,此时只能是左滑,最终设置按钮的总宽度,同时为负数
|
||||||
|
if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
|
||||||
|
// 只要是在滑过程中,就不断移动菜单的内容部分,从而使隐藏的菜单显示出来
|
||||||
|
moveSwipeAction(moveX, instance, ownerInstance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触摸结束
|
||||||
|
function touchend(event, ownerInstance) {
|
||||||
|
// 触发事件的组件的ComponentDescriptor实例
|
||||||
|
var instance = event.instance
|
||||||
|
// wxs内的局部变量快照
|
||||||
|
var state = instance.getState()
|
||||||
|
if (!state.moving || state.disabled) return
|
||||||
|
var touches = event.changedTouches ? event.changedTouches[0] : {}
|
||||||
|
var pageX = touches.pageX
|
||||||
|
var pageY = touches.pageY
|
||||||
|
var moveX = pageX - state.startX
|
||||||
|
if (state.status === 'open') {
|
||||||
|
// 在展开的状态下,继续左滑,无需操作
|
||||||
|
if (moveX < 0) return
|
||||||
|
// 在开启状态下,点击一下内容区域,moveX为0,也即没有进行移动,这时执行收起菜单逻辑
|
||||||
|
if (moveX === 0) {
|
||||||
|
return closeSwipeAction(instance, ownerInstance)
|
||||||
|
}
|
||||||
|
// 在开启状态下,滑动距离小于阈值,则默认为不关闭,同时恢复原来的打开状态
|
||||||
|
if (Math.abs(moveX) < state.threshold) {
|
||||||
|
openSwipeAction(instance, ownerInstance)
|
||||||
|
} else {
|
||||||
|
// 如果滑动距离大于阈值,则执行收起逻辑
|
||||||
|
closeSwipeAction(instance, ownerInstance)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 在关闭的状态下,右滑,无需操作
|
||||||
|
if (moveX > 0) return
|
||||||
|
// 理由同上
|
||||||
|
if (Math.abs(moveX) < state.threshold) {
|
||||||
|
closeSwipeAction(instance, ownerInstance)
|
||||||
|
} else {
|
||||||
|
openSwipeAction(instance, ownerInstance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取过渡时间
|
||||||
|
function getDuration(value) {
|
||||||
|
if (value.toString().indexOf('s') >= 0) return value
|
||||||
|
return value > 30 ? value + 'ms' : value + 's'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滑动结束时判断滑动的方向
|
||||||
|
function getMoveDirection(instance, ownerInstance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动滑动选择器内容区域,同时显示出其隐藏的菜单
|
||||||
|
function moveSwipeAction(moveX, instance, ownerInstance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||||
|
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||||
|
var len = buttons.length
|
||||||
|
var previewButtonsMoveX = 0
|
||||||
|
|
||||||
|
// 设置菜单内容部分的偏移
|
||||||
|
instance.requestAnimationFrame(function() {
|
||||||
|
instance.setStyle({
|
||||||
|
// 设置translateX的值
|
||||||
|
'transition': 'none',
|
||||||
|
transform: 'translateX(' + moveX + 'px)',
|
||||||
|
'-webkit-transform': 'translateX(' + moveX + 'px)'
|
||||||
|
})
|
||||||
|
// 折叠按钮动画
|
||||||
|
for (var i = len - 1; i >= 0; i--) {
|
||||||
|
// 通过比例,得出元素自身该移动的距离
|
||||||
|
var translateX = state.buttons[i].width / state.buttonsWidth * moveX
|
||||||
|
// 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
|
||||||
|
var realTranslateX = translateX + previewButtonsMoveX
|
||||||
|
buttons[i].setStyle({
|
||||||
|
// 在移动期间,不能使用过渡效果,否则会造成卡顿,本质原因是每次移动一点,就要花一定时间去过渡这个过程
|
||||||
|
'transition': 'none',
|
||||||
|
'transform': 'translateX(' + realTranslateX + 'px)',
|
||||||
|
'-webkit-transform': 'translateX(' + realTranslateX + 'px)'
|
||||||
|
})
|
||||||
|
// 记录本按钮之前的所有按钮的移动距离之和
|
||||||
|
previewButtonsMoveX += translateX
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一次性展开滑动菜单
|
||||||
|
function openSwipeAction(instance, ownerInstance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||||
|
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||||
|
var len = buttons.length
|
||||||
|
// 处理duration单位问题
|
||||||
|
const duration = getDuration(state.duration)
|
||||||
|
// 展开过程中,是向左移动,所以X的偏移应该为负值
|
||||||
|
var buttonsWidth = -state.buttonsWidth
|
||||||
|
var previewButtonsMoveX = 0
|
||||||
|
instance.requestAnimationFrame(function() {
|
||||||
|
// 设置菜单主体内容
|
||||||
|
instance.setStyle({
|
||||||
|
'transition': 'transform ' + duration,
|
||||||
|
'transform': 'translateX(' + buttonsWidth + 'px)',
|
||||||
|
'-webkit-transform': 'translateX(' + buttonsWidth + 'px)',
|
||||||
|
})
|
||||||
|
// 设置各个隐藏的按钮为展开的状态
|
||||||
|
for (var i = len - 1; i >= 0; i--) {
|
||||||
|
// 通过比例,得出元素自身该移动的距离
|
||||||
|
var translateX = state.buttons[i].width / state.buttonsWidth * buttonsWidth
|
||||||
|
// 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
|
||||||
|
var realTranslateX = translateX + previewButtonsMoveX
|
||||||
|
buttons[i].setStyle({
|
||||||
|
// 在移动期间,需要加上动画效果
|
||||||
|
'transition': 'transform ' + duration,
|
||||||
|
'transform': 'translateX(' + realTranslateX + 'px)',
|
||||||
|
'-webkit-transform': 'translateX(' + realTranslateX + 'px)'
|
||||||
|
})
|
||||||
|
// 记录本按钮之前的所有按钮的移动距离之和
|
||||||
|
previewButtonsMoveX += translateX
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setStatus('open', instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记菜单的当前状态,open-已经打开,close-已经关闭
|
||||||
|
function setStatus(status, instance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
state.status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一次性收起滑动菜单
|
||||||
|
function closeSwipeAction(instance, ownerInstance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||||
|
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||||
|
var len = buttons.length
|
||||||
|
// 处理duration单位问题
|
||||||
|
const duration = getDuration(state.duration)
|
||||||
|
instance.requestAnimationFrame(function() {
|
||||||
|
// 设置菜单主体内容
|
||||||
|
instance.setStyle({
|
||||||
|
'transition': 'transform ' + duration,
|
||||||
|
'transform': 'translateX(0px)',
|
||||||
|
'-webkit-transform': 'translateX(0px)'
|
||||||
|
})
|
||||||
|
// 设置各个隐藏的按钮为收起的状态
|
||||||
|
for (var i = len - 1; i >= 0; i--) {
|
||||||
|
buttons[i].setStyle({
|
||||||
|
'transition': 'transform ' + duration,
|
||||||
|
'transform': 'translateX(0px)',
|
||||||
|
'-webkit-transform': 'translateX(0px)'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setStatus('close', instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show的状态发生变化
|
||||||
|
function showChange(newValue, oldValue, ownerInstance, instance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
if (state.disabled) return
|
||||||
|
// 打开或关闭单元格
|
||||||
|
if (newValue) {
|
||||||
|
openSwipeAction(instance, ownerInstance)
|
||||||
|
} else {
|
||||||
|
closeSwipeAction(instance, ownerInstance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 菜单尺寸发生变化
|
||||||
|
function sizeChange(newValue, oldValue, ownerInstance, instance) {
|
||||||
|
// wxs内的局部变量快照
|
||||||
|
var state = instance.getState()
|
||||||
|
state.disabled = newValue.disabled
|
||||||
|
state.duration = newValue.duration
|
||||||
|
state.show = newValue.show
|
||||||
|
state.threshold = newValue.threshold
|
||||||
|
state.buttons = newValue.buttons
|
||||||
|
|
||||||
|
var len = state.buttons.length
|
||||||
|
if (len) {
|
||||||
|
var buttonsWidth = 0
|
||||||
|
var buttons = newValue.buttons
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
buttonsWidth += buttons[i].width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.buttonsWidth = buttonsWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
touchstart: touchstart,
|
||||||
|
touchmove: touchmove,
|
||||||
|
touchend: touchend,
|
||||||
|
sizeChange: sizeChange
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
/**
|
||||||
|
* 此为wxs模块,只支持APP-VUE,微信和QQ小程序以及H5平台
|
||||||
|
* wxs内部不支持es6语法,变量只能使用var定义,无法使用解构,箭头函数等特性
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 开始触摸
|
||||||
|
function touchstart(event, ownerInstance) {
|
||||||
|
// 触发事件的组件的ComponentDescriptor实例
|
||||||
|
var instance = event.instance
|
||||||
|
// wxs内的局部变量快照,此快照是属于整个组件的,在touchstart和touchmove事件中都能获取到相同的结果
|
||||||
|
var state = instance.getState()
|
||||||
|
if (state.disabled) return
|
||||||
|
var touches = event.touches
|
||||||
|
// 如果进行的是多指触控,不允许进行操作
|
||||||
|
if (touches && touches.length > 1) return
|
||||||
|
// 标识当前为滑动中状态
|
||||||
|
state.moving = true
|
||||||
|
// 记录触摸开始点的坐标值
|
||||||
|
state.startX = touches[0].pageX
|
||||||
|
state.startY = touches[0].pageY
|
||||||
|
|
||||||
|
ownerInstance.callMethod('closeOther')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触摸滑动
|
||||||
|
function touchmove(event, ownerInstance) {
|
||||||
|
// 触发事件的组件的ComponentDescriptor实例
|
||||||
|
var instance = event.instance
|
||||||
|
// wxs内的局部变量快照
|
||||||
|
var state = instance.getState()
|
||||||
|
if (state.disabled || !state.moving) return
|
||||||
|
var touches = event.touches
|
||||||
|
var pageX = touches[0].pageX
|
||||||
|
var pageY = touches[0].pageY
|
||||||
|
var moveX = pageX - state.startX
|
||||||
|
var moveY = pageY - state.startY
|
||||||
|
var buttonsWidth = state.buttonsWidth
|
||||||
|
|
||||||
|
// 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
|
||||||
|
if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {
|
||||||
|
event.preventDefault && event.preventDefault()
|
||||||
|
event.stopPropagation && event.stopPropagation()
|
||||||
|
}
|
||||||
|
// 如果移动的X轴距离小于Y轴距离,也即终点位置与起点位置连线,与Y轴夹角小于45度时,认为是页面上下滑动,而不是左右滑动单元格
|
||||||
|
if (Math.abs(moveX) < Math.abs(moveY)) return
|
||||||
|
|
||||||
|
// 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
|
||||||
|
// 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,最好的办法就是
|
||||||
|
// 在超出后,设置为0
|
||||||
|
if (state.status === 'open') {
|
||||||
|
// 在开启状态下,向左滑动,需忽略
|
||||||
|
if (moveX < 0) moveX = 0
|
||||||
|
// 想要收起菜单,最大能移动的距离为按钮的总宽度
|
||||||
|
if (moveX > buttonsWidth) moveX = buttonsWidth
|
||||||
|
// 如果是已经打开了的状态,向左滑动时,移动收起菜单
|
||||||
|
moveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)
|
||||||
|
} else {
|
||||||
|
// 关闭状态下,右滑动需忽略
|
||||||
|
if (moveX > 0) moveX = 0
|
||||||
|
// 滑动的距离不允许超过所有按钮的总宽度,此时只能是左滑,最终设置按钮的总宽度,同时为负数
|
||||||
|
if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
|
||||||
|
// 只要是在滑过程中,就不断移动单元格内容部分,从而使隐藏的菜单显示出来
|
||||||
|
moveSwipeAction(moveX, instance, ownerInstance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触摸结束
|
||||||
|
function touchend(event, ownerInstance) {
|
||||||
|
// 触发事件的组件的ComponentDescriptor实例
|
||||||
|
var instance = event.instance
|
||||||
|
// wxs内的局部变量快照
|
||||||
|
var state = instance.getState()
|
||||||
|
if (!state.moving || state.disabled) return
|
||||||
|
var touches = event.changedTouches ? event.changedTouches[0] : {}
|
||||||
|
var pageX = touches.pageX
|
||||||
|
var pageY = touches.pageY
|
||||||
|
var moveX = pageX - state.startX
|
||||||
|
if (state.status === 'open') {
|
||||||
|
// 在展开的状态下,继续左滑,无需操作
|
||||||
|
if (moveX < 0) return
|
||||||
|
// 在开启状态下,点击一下内容区域,moveX为0,也即没有进行移动,这时执行收起菜单逻辑
|
||||||
|
if (moveX === 0) {
|
||||||
|
return closeSwipeAction(instance, ownerInstance)
|
||||||
|
}
|
||||||
|
// 在开启状态下,滑动距离小于阈值,则默认为不关闭,同时恢复原来的打开状态
|
||||||
|
if (Math.abs(moveX) < state.threshold) {
|
||||||
|
openSwipeAction(instance, ownerInstance)
|
||||||
|
} else {
|
||||||
|
// 如果滑动距离大于阈值,则执行收起逻辑
|
||||||
|
closeSwipeAction(instance, ownerInstance)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 在关闭的状态下,右滑,无需操作
|
||||||
|
if (moveX > 0) return
|
||||||
|
// 理由同上
|
||||||
|
if (Math.abs(moveX) < state.threshold) {
|
||||||
|
closeSwipeAction(instance, ownerInstance)
|
||||||
|
} else {
|
||||||
|
openSwipeAction(instance, ownerInstance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取过渡时间
|
||||||
|
function getDuration(value) {
|
||||||
|
if (value.toString().indexOf('s') >= 0) return value
|
||||||
|
return value > 30 ? value + 'ms' : value + 's'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滑动结束时判断滑动的方向
|
||||||
|
function getMoveDirection(instance, ownerInstance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动滑动选择器内容区域,同时显示出其隐藏的菜单
|
||||||
|
function moveSwipeAction(moveX, instance, ownerInstance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||||
|
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||||
|
|
||||||
|
// 设置菜单内容部分的偏移
|
||||||
|
instance.requestAnimationFrame(function() {
|
||||||
|
instance.setStyle({
|
||||||
|
// 设置translateX的值
|
||||||
|
'transition': 'none',
|
||||||
|
transform: 'translateX(' + moveX + 'px)',
|
||||||
|
'-webkit-transform': 'translateX(' + moveX + 'px)'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一次性展开滑动菜单
|
||||||
|
function openSwipeAction(instance, ownerInstance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||||
|
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||||
|
// 处理duration单位问题
|
||||||
|
var duration = getDuration(state.duration)
|
||||||
|
// 展开过程中,是向左移动,所以X的偏移应该为负值
|
||||||
|
var buttonsWidth = -state.buttonsWidth
|
||||||
|
instance.requestAnimationFrame(function() {
|
||||||
|
// 设置菜单主体内容
|
||||||
|
instance.setStyle({
|
||||||
|
'transition': 'transform ' + duration,
|
||||||
|
'transform': 'translateX(' + buttonsWidth + 'px)',
|
||||||
|
'-webkit-transform': 'translateX(' + buttonsWidth + 'px)',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setStatus('open', instance, ownerInstance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记菜单的当前状态,open-已经打开,close-已经关闭
|
||||||
|
function setStatus(status, instance, ownerInstance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
state.status = status
|
||||||
|
ownerInstance.callMethod('setState', status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一次性收起滑动菜单
|
||||||
|
function closeSwipeAction(instance, ownerInstance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||||
|
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||||
|
var len = buttons.length
|
||||||
|
// 处理duration单位问题
|
||||||
|
var duration = getDuration(state.duration)
|
||||||
|
instance.requestAnimationFrame(function() {
|
||||||
|
// 设置菜单主体内容
|
||||||
|
instance.setStyle({
|
||||||
|
'transition': 'transform ' + duration,
|
||||||
|
'transform': 'translateX(0px)',
|
||||||
|
'-webkit-transform': 'translateX(0px)'
|
||||||
|
})
|
||||||
|
// 设置各个隐藏的按钮为收起的状态
|
||||||
|
for (var i = len - 1; i >= 0; i--) {
|
||||||
|
buttons[i].setStyle({
|
||||||
|
'transition': 'transform ' + duration,
|
||||||
|
'transform': 'translateX(0px)',
|
||||||
|
'-webkit-transform': 'translateX(0px)'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setStatus('close', instance, ownerInstance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// status的状态发生变化
|
||||||
|
function statusChange(newValue, oldValue, ownerInstance, instance) {
|
||||||
|
var state = instance.getState()
|
||||||
|
if (state.disabled) return
|
||||||
|
// 打开或关闭单元格
|
||||||
|
if (newValue === 'close' && state.status === 'open') {
|
||||||
|
closeSwipeAction(instance, ownerInstance)
|
||||||
|
} else if(newValue === 'open' && state.status === 'close') {
|
||||||
|
openSwipeAction(instance, ownerInstance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 菜单尺寸发生变化
|
||||||
|
function sizeChange(newValue, oldValue, ownerInstance, instance) {
|
||||||
|
// wxs内的局部变量快照
|
||||||
|
var state = instance.getState()
|
||||||
|
state.disabled = newValue.disabled
|
||||||
|
state.duration = newValue.duration
|
||||||
|
state.show = newValue.show
|
||||||
|
state.threshold = newValue.threshold
|
||||||
|
state.buttons = newValue.buttons
|
||||||
|
|
||||||
|
if (state.buttons) {
|
||||||
|
var len = state.buttons.length
|
||||||
|
var buttonsWidth = 0
|
||||||
|
var buttons = newValue.buttons
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
buttonsWidth += buttons[i].width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.buttonsWidth = buttonsWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
touchstart: touchstart,
|
||||||
|
touchmove: touchmove,
|
||||||
|
touchend: touchend,
|
||||||
|
sizeChange: sizeChange,
|
||||||
|
statusChange: statusChange
|
||||||
|
}
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
// nvue操作dom的库,用于获取dom的尺寸信息
|
||||||
|
const dom = uni.requireNativePlugin('dom')
|
||||||
|
// nvue中用于操作元素动画的库,类似于uni.animation,只不过uni.animation不能用于nvue
|
||||||
|
const animation = uni.requireNativePlugin('animation')
|
||||||
|
import { sleep } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 是否滑动中
|
||||||
|
moving: false,
|
||||||
|
// 状态,open-打开状态,close-关闭状态
|
||||||
|
status: 'close',
|
||||||
|
// 开始触摸点的X和Y轴坐标
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
// 所有隐藏按钮的尺寸信息数组
|
||||||
|
buttons: [],
|
||||||
|
// 所有按钮的总宽度
|
||||||
|
buttonsWidth: 0,
|
||||||
|
// 记录上一次移动的位置值
|
||||||
|
moveX: 0,
|
||||||
|
// 记录上一次滑动的位置,用于前后两次做对比,如果移动的距离小于某一阈值,则认为前后之间没有移动,为了解决可能存在的通信阻塞问题
|
||||||
|
lastX: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 获取过渡时间
|
||||||
|
getDuratin() {
|
||||||
|
let duration = String(this.duration)
|
||||||
|
// 如果ms为单位,返回ms的数值部分
|
||||||
|
if (duration.indexOf('ms') >= 0) return parseInt(duration)
|
||||||
|
// 如果s为单位,为了得到ms的数值,需要乘以1000
|
||||||
|
if (duration.indexOf('s') >= 0) return parseInt(duration) * 1000
|
||||||
|
// 如果值传了数值,且小于30,认为是s单位
|
||||||
|
duration = Number(duration)
|
||||||
|
return duration < 30 ? duration * 1000 : duration
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show: {
|
||||||
|
immediate: true,
|
||||||
|
handler(n) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
sleep(20).then(() => {
|
||||||
|
this.queryRect()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.closeSwipeAction()
|
||||||
|
},
|
||||||
|
// 触摸单元格
|
||||||
|
touchstart(event) {
|
||||||
|
if (this.disabled) return
|
||||||
|
this.closeOther()
|
||||||
|
const { touches } = event
|
||||||
|
// 记录触摸开始点的坐标值
|
||||||
|
this.startX = touches[0].pageX
|
||||||
|
this.startY = touches[0].pageY
|
||||||
|
},
|
||||||
|
// // 触摸滑动
|
||||||
|
touchmove(event) {
|
||||||
|
if (this.disabled) return
|
||||||
|
const { touches } = event
|
||||||
|
const { pageX } = touches[0]
|
||||||
|
const { pageY } = touches[0]
|
||||||
|
let moveX = pageX - this.startX
|
||||||
|
const moveY = pageY - this.startY
|
||||||
|
const { buttonsWidth } = this
|
||||||
|
const len = this.buttons.length
|
||||||
|
|
||||||
|
// 判断前后两次的移动距离,如果小于一定值,则不进行移动处理
|
||||||
|
if (Math.abs(pageX - this.lastX) < 0.3) return
|
||||||
|
this.lastX = pageX
|
||||||
|
|
||||||
|
// 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
|
||||||
|
if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > this.threshold) {
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
// 如果移动的X轴距离小于Y轴距离,也即终点位置与起点位置连线,与Y轴夹角小于45度时,认为是页面上下滑动,而不是左右滑动单元格
|
||||||
|
if (Math.abs(moveX) < Math.abs(moveY)) return
|
||||||
|
|
||||||
|
// 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
|
||||||
|
// 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,最好的办法就是
|
||||||
|
// 在超出后,设置为0
|
||||||
|
if (this.status === 'open') {
|
||||||
|
// 在开启状态下,向左滑动,需忽略
|
||||||
|
if (moveX < 0) moveX = 0
|
||||||
|
// 想要收起菜单,最大能移动的距离为按钮的总宽度
|
||||||
|
if (moveX > buttonsWidth) moveX = buttonsWidth
|
||||||
|
// 如果是已经打开了的状态,向左滑动时,移动收起菜单
|
||||||
|
this.moveSwipeAction(-buttonsWidth + moveX)
|
||||||
|
} else {
|
||||||
|
// 关闭状态下,右滑动需忽略
|
||||||
|
if (moveX > 0) moveX = 0
|
||||||
|
// 滑动的距离不允许超过所有按钮的总宽度,此时只能是左滑,最终设置按钮的总宽度,同时为负数
|
||||||
|
if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
|
||||||
|
// 只要是在滑过程中,就不断移动菜单的内容部分,从而使隐藏的菜单显示出来
|
||||||
|
this.moveSwipeAction(moveX)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 单元格结束触摸
|
||||||
|
touchend(event) {
|
||||||
|
if (this.disabled) return
|
||||||
|
const touches = event.changedTouches ? event.changedTouches[0] : {}
|
||||||
|
const { pageX } = touches
|
||||||
|
const { pageY } = touches
|
||||||
|
const { buttonsWidth } = this
|
||||||
|
this.moveX = pageX - this.startX
|
||||||
|
if (this.status === 'open') {
|
||||||
|
// 在展开的状态下,继续左滑,无需操作
|
||||||
|
if (this.moveX < 0) this.moveX = 0
|
||||||
|
if (this.moveX > buttonsWidth) this.moveX = buttonsWidth
|
||||||
|
// 在开启状态下,点击一下内容区域,moveX为0,也即没有进行移动,这时执行收起菜单逻辑
|
||||||
|
if (this.moveX === 0) {
|
||||||
|
return this.closeSwipeAction()
|
||||||
|
}
|
||||||
|
// 在开启状态下,滑动距离小于阈值,则默认为不关闭,同时恢复原来的打开状态
|
||||||
|
if (Math.abs(this.moveX) < this.threshold) {
|
||||||
|
this.openSwipeAction()
|
||||||
|
} else {
|
||||||
|
// 如果滑动距离大于阈值,则执行收起逻辑
|
||||||
|
this.closeSwipeAction()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 在关闭的状态下,右滑,无需操作
|
||||||
|
if (this.moveX >= 0) this.moveX = 0
|
||||||
|
if (this.moveX <= -buttonsWidth) this.moveX = -buttonsWidth
|
||||||
|
// 理由同上
|
||||||
|
if (Math.abs(this.moveX) < this.threshold) {
|
||||||
|
this.closeSwipeAction()
|
||||||
|
} else {
|
||||||
|
this.openSwipeAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 移动滑动选择器内容区域,同时显示出其隐藏的菜单
|
||||||
|
moveSwipeAction(moveX) {
|
||||||
|
if (this.moving) return
|
||||||
|
this.moving = true
|
||||||
|
|
||||||
|
let previewButtonsMoveX = 0
|
||||||
|
const len = this.buttons.length
|
||||||
|
animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {
|
||||||
|
styles: {
|
||||||
|
transform: `translateX(${moveX}px)`
|
||||||
|
},
|
||||||
|
timingFunction: 'linear'
|
||||||
|
}, () => {
|
||||||
|
this.moving = false
|
||||||
|
})
|
||||||
|
// 按钮的组的长度
|
||||||
|
for (let i = len - 1; i >= 0; i--) {
|
||||||
|
const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref
|
||||||
|
// 通过比例,得出元素自身该移动的距离
|
||||||
|
const translateX = this.buttons[i].width / this.buttonsWidth * moveX
|
||||||
|
// 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
|
||||||
|
const realTranslateX = translateX + previewButtonsMoveX
|
||||||
|
animation.transition(buttonRef, {
|
||||||
|
styles: {
|
||||||
|
transform: `translateX(${realTranslateX}px)`
|
||||||
|
},
|
||||||
|
duration: 0,
|
||||||
|
delay: 0,
|
||||||
|
timingFunction: 'linear'
|
||||||
|
}, () => {})
|
||||||
|
// 记录本按钮之前的所有按钮的移动距离之和
|
||||||
|
previewButtonsMoveX += translateX
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 关闭菜单
|
||||||
|
closeSwipeAction() {
|
||||||
|
if (this.status === 'close') return
|
||||||
|
this.moving = true
|
||||||
|
const { buttonsWidth } = this
|
||||||
|
animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {
|
||||||
|
styles: {
|
||||||
|
transform: 'translateX(0px)'
|
||||||
|
},
|
||||||
|
duration: this.getDuratin,
|
||||||
|
timingFunction: 'ease-in-out'
|
||||||
|
}, () => {
|
||||||
|
this.status = 'close'
|
||||||
|
this.moving = false
|
||||||
|
this.closeHandler()
|
||||||
|
})
|
||||||
|
// 按钮的组的长度
|
||||||
|
const len = this.buttons.length
|
||||||
|
for (let i = len - 1; i >= 0; i--) {
|
||||||
|
const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref
|
||||||
|
// 如果不满足边界条件,返回
|
||||||
|
if (this.buttons.length === 0 || !this.buttons[i] || !this.buttons[i].width) return
|
||||||
|
|
||||||
|
animation.transition(buttonRef, {
|
||||||
|
styles: {
|
||||||
|
transform: 'translateX(0px)'
|
||||||
|
},
|
||||||
|
duration: this.getDuratin,
|
||||||
|
timingFunction: 'ease-in-out'
|
||||||
|
}, () => {})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 打开菜单
|
||||||
|
openSwipeAction() {
|
||||||
|
if (this.status === 'open') return
|
||||||
|
this.moving = true
|
||||||
|
const buttonsWidth = -this.buttonsWidth
|
||||||
|
let previewButtonsMoveX = 0
|
||||||
|
animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {
|
||||||
|
styles: {
|
||||||
|
transform: `translateX(${buttonsWidth}px)`
|
||||||
|
},
|
||||||
|
duration: this.getDuratin,
|
||||||
|
timingFunction: 'ease-in-out'
|
||||||
|
}, () => {
|
||||||
|
this.status = 'open'
|
||||||
|
this.moving = false
|
||||||
|
this.openHandler()
|
||||||
|
})
|
||||||
|
// 按钮的组的长度
|
||||||
|
const len = this.buttons.length
|
||||||
|
for (let i = len - 1; i >= 0; i--) {
|
||||||
|
const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref
|
||||||
|
// 如果不满足边界条件,返回
|
||||||
|
if (this.buttons.length === 0 || !this.buttons[i] || !this.buttons[i].width) return
|
||||||
|
// 通过比例,得出元素自身该移动的距离
|
||||||
|
const translateX = this.buttons[i].width / this.buttonsWidth * buttonsWidth
|
||||||
|
// 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
|
||||||
|
const realTranslateX = translateX + previewButtonsMoveX
|
||||||
|
animation.transition(buttonRef, {
|
||||||
|
styles: {
|
||||||
|
transform: `translateX(${realTranslateX}px)`
|
||||||
|
},
|
||||||
|
duration: this.getDuratin,
|
||||||
|
timingFunction: 'ease-in-out'
|
||||||
|
}, () => {})
|
||||||
|
previewButtonsMoveX += translateX
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 查询按钮节点信息
|
||||||
|
queryRect() {
|
||||||
|
// 历遍所有按钮数组,通过getRectByDom返回一个promise
|
||||||
|
const promiseAll = this.rightOptions.map((item, index) => this.getRectByDom(this.$refs[`uv-swipe-action-item__right__button-${index}`][0]))
|
||||||
|
// 通过promise.all方法,让所有按钮的查询结果返回一个数组的形式
|
||||||
|
Promise.all(promiseAll).then((sizes) => {
|
||||||
|
this.buttons = sizes
|
||||||
|
// 计算所有按钮总宽度
|
||||||
|
this.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 通过nvue的dom模块,查询节点信息
|
||||||
|
getRectByDom(ref) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
dom.getComponentRect(ref, (res) => {
|
||||||
|
resolve(res.size)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
// nvue操作dom的库,用于获取dom的尺寸信息
|
||||||
|
const dom = uni.requireNativePlugin('dom');
|
||||||
|
const bindingX = uni.requireNativePlugin('bindingx');
|
||||||
|
const animation = uni.requireNativePlugin('animation');
|
||||||
|
import { getDuration, getPx } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 所有按钮的总宽度
|
||||||
|
buttonsWidth: 0,
|
||||||
|
// 是否正在移动中
|
||||||
|
moving: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 获取过渡时间
|
||||||
|
getDuratin() {
|
||||||
|
let duration = String(this.duration)
|
||||||
|
// 如果ms为单位,返回ms的数值部分
|
||||||
|
if (duration.indexOf('ms') >= 0) return parseInt(duration)
|
||||||
|
// 如果s为单位,为了得到ms的数值,需要乘以1000
|
||||||
|
if (duration.indexOf('s') >= 0) return parseInt(duration) * 1000
|
||||||
|
// 如果值传了数值,且小于30,认为是s单位
|
||||||
|
duration = Number(duration)
|
||||||
|
return duration < 30 ? duration * 1000 : duration
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show(n) {
|
||||||
|
if(n) {
|
||||||
|
this.moveCellByAnimation('open')
|
||||||
|
} else {
|
||||||
|
this.moveCellByAnimation('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
setTimeout(()=>{
|
||||||
|
this.initialize()
|
||||||
|
},20)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initialize() {
|
||||||
|
this.queryRect()
|
||||||
|
},
|
||||||
|
// 关闭单元格,用于打开一个,自动关闭其他单元格的场景
|
||||||
|
closeHandler() {
|
||||||
|
if(this.status === 'open') {
|
||||||
|
// 如果在打开状态下,进行点击的话,直接关闭单元格
|
||||||
|
return this.moveCellByAnimation('close') && this.unbindBindingX()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 点击单元格
|
||||||
|
clickHandler() {
|
||||||
|
// 如果在移动中被点击,进行忽略
|
||||||
|
if(this.moving) return
|
||||||
|
// 尝试关闭其他打开的单元格
|
||||||
|
this.parent && this.parent.closeOther(this)
|
||||||
|
if(this.status === 'open') {
|
||||||
|
// 如果在打开状态下,进行点击的话,直接关闭单元格
|
||||||
|
return this.moveCellByAnimation('close') && this.unbindBindingX()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 滑动单元格
|
||||||
|
onTouchstart(e) {
|
||||||
|
// 如果当前正在移动中,或者disabled状态,则返回
|
||||||
|
if(this.moving || this.disabled) {
|
||||||
|
return this.unbindBindingX()
|
||||||
|
}
|
||||||
|
if(this.status === 'open') {
|
||||||
|
// 如果在打开状态下,进行点击的话,直接关闭单元格
|
||||||
|
return this.moveCellByAnimation('close') && this.unbindBindingX()
|
||||||
|
}
|
||||||
|
// 特殊情况下,e可能不为一个对象
|
||||||
|
e?.stopPropagation && e.stopPropagation()
|
||||||
|
e?.preventDefault && e.preventDefault()
|
||||||
|
this.moving = true
|
||||||
|
// 获取元素ref
|
||||||
|
const content = this.getContentRef()
|
||||||
|
let expression = `min(max(${-this.buttonsWidth}, x), 0)`
|
||||||
|
// 尝试关闭其他打开的单元格
|
||||||
|
this.parent && this.parent.closeOther(this)
|
||||||
|
// 阿里为了KPI而开源的BindingX
|
||||||
|
this.panEvent = bindingX.bind({
|
||||||
|
anchor: content,
|
||||||
|
eventType: 'pan',
|
||||||
|
props: [{
|
||||||
|
element: content,
|
||||||
|
// 绑定width属性,设置其宽度值
|
||||||
|
property: 'transform.translateX',
|
||||||
|
expression
|
||||||
|
}]
|
||||||
|
}, (res) => {
|
||||||
|
this.moving = false
|
||||||
|
if (res.state === 'end' || res.state === 'exit') {
|
||||||
|
const deltaX = res.deltaX
|
||||||
|
if(deltaX <= -this.buttonsWidth || deltaX >= 0) {
|
||||||
|
// 如果触摸滑动的过程中,大于单元格的总宽度,或者大于0,意味着已经动过滑动达到了打开或者关闭的状态
|
||||||
|
// 这里直接进行状态的标记
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.status = deltaX <= -this.buttonsWidth ? 'open' : 'close'
|
||||||
|
})
|
||||||
|
} else if(Math.abs(deltaX) > getPx(this.threshold)) {
|
||||||
|
// 在移动大于阈值、并且小于总按钮宽度时,进行自动打开或者关闭
|
||||||
|
// 移动距离大于0时,意味着需要关闭状态
|
||||||
|
if(Math.abs(deltaX) < this.buttonsWidth) {
|
||||||
|
this.moveCellByAnimation(deltaX > 0 ? 'close' : 'open')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 在小于阈值时,进行关闭操作(如果在打开状态下,将不会执行bindingX)
|
||||||
|
this.moveCellByAnimation('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 释放bindingX
|
||||||
|
unbindBindingX() {
|
||||||
|
// 释放上一次的资源
|
||||||
|
if (this?.panEvent?.token != 0) {
|
||||||
|
bindingX.unbind({
|
||||||
|
token: this.panEvent?.token,
|
||||||
|
// pan为手势事件
|
||||||
|
eventType: 'pan'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 查询按钮节点信息
|
||||||
|
queryRect() {
|
||||||
|
// 历遍所有按钮数组,通过getRectByDom返回一个promise
|
||||||
|
const promiseAll = this.options.map(async(item, index) => {
|
||||||
|
return await this.getRectByDom(this.$refs[`uv-swipe-action-item__right__button-${index}`][0])
|
||||||
|
})
|
||||||
|
// 通过promise.all方法,让所有按钮的查询结果返回一个数组的形式
|
||||||
|
Promise.all(promiseAll).then(sizes => {
|
||||||
|
this.buttons = sizes
|
||||||
|
// 计算所有按钮总宽度
|
||||||
|
this.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 通过nvue的dom模块,查询节点信息
|
||||||
|
getRectByDom(ref) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dom.getComponentRect(ref, res => {
|
||||||
|
resolve(res.size)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 移动单元格到左边或者右边尽头
|
||||||
|
moveCellByAnimation(status = 'open') {
|
||||||
|
if(this.moving) return
|
||||||
|
// 标识当前状态
|
||||||
|
this.moveing = true
|
||||||
|
const content = this.getContentRef()
|
||||||
|
const x = status === 'open' ? -this.buttonsWidth : 0
|
||||||
|
animation.transition(content, {
|
||||||
|
styles: {
|
||||||
|
transform: `translateX(${x}px)`,
|
||||||
|
},
|
||||||
|
duration: getDuration(this.duration, false),
|
||||||
|
timingFunction: 'ease-in-out'
|
||||||
|
}, () => {
|
||||||
|
this.moving = false
|
||||||
|
this.status = status
|
||||||
|
this.unbindBindingX()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 获取元素ref
|
||||||
|
getContentRef() {
|
||||||
|
return this.$refs['uv-swipe-action-item__content'].ref
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// #ifdef VUE2
|
||||||
|
beforeDestroy() {
|
||||||
|
this.unbindBindingX()
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// #ifdef VUE3
|
||||||
|
unmounted() {
|
||||||
|
this.unbindBindingX()
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
// 控制打开或者关闭
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 标识符,如果是v-for,可用index索引值
|
||||||
|
name: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 是否禁用
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 是否自动关闭其他swipe按钮组
|
||||||
|
autoClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// 滑动距离阈值,只有大于此值,才被认为是要打开菜单
|
||||||
|
threshold: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
// 右侧按钮内容
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// 动画过渡时间,单位ms
|
||||||
|
duration: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 300
|
||||||
|
},
|
||||||
|
...uni.$uv?.props?.swipeActionItem
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uv-swipe-action-item" ref="uv-swipe-action-item">
|
||||||
|
<view class="uv-swipe-action-item__right">
|
||||||
|
<slot name="button">
|
||||||
|
<view v-for="(item,index) in options" :key="index" class="uv-swipe-action-item__right__button"
|
||||||
|
:ref="`uv-swipe-action-item__right__button-${index}`" :style="[{
|
||||||
|
alignItems: item.style && item.style.borderRadius ? 'center' : 'stretch'
|
||||||
|
}]" @tap="buttonClickHandler(item, index)">
|
||||||
|
<view class="uv-swipe-action-item__right__button__wrapper" :style="[{
|
||||||
|
backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',
|
||||||
|
borderRadius: item.style && item.style.borderRadius ? item.style.borderRadius : '0',
|
||||||
|
padding: item.style && item.style.borderRadius ? '0' : '0 15px',
|
||||||
|
}, item.style]">
|
||||||
|
<uv-icon v-if="item.icon" :name="item.icon"
|
||||||
|
:color="item.style && item.style.color ? item.style.color : '#ffffff'"
|
||||||
|
:size="item.iconSize ? $uv.addUnit(item.iconSize) : item.style && item.style.fontSize ? $uv.getPx(item.style.fontSize) * 1.2 : 17"
|
||||||
|
:customStyle="{
|
||||||
|
marginRight: item.text ? '2px' : 0
|
||||||
|
}"></uv-icon>
|
||||||
|
<text v-if="item.text" class="uv-swipe-action-item__right__button__wrapper__text uv-line-1"
|
||||||
|
:style="[{
|
||||||
|
color: item.style && item.style.color ? item.style.color : '#ffffff',
|
||||||
|
fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px',
|
||||||
|
lineHeight: item.style && item.style.fontSize ? item.style.fontSize : '16px',
|
||||||
|
}]">{{ item.text }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
<!-- #ifndef APP-NVUE -->
|
||||||
|
<view class="uv-swipe-action-item__content" @touchstart="wxs.touchstart" @touchmove="wxs.touchmove"
|
||||||
|
@touchend="wxs.touchend" :status="status" :change:status="wxs.statusChange" :size="size"
|
||||||
|
:change:size="wxs.sizeChange">
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef APP-NVUE -->
|
||||||
|
<view class="uv-swipe-action-item__content" ref="uv-swipe-action-item__content" @panstart="onTouchstart"
|
||||||
|
@tap="clickHandler">
|
||||||
|
<!-- #endif -->
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<!-- #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ -->
|
||||||
|
<script src="./index.wxs" module="wxs" lang="wxs"></script>
|
||||||
|
<!-- #endif -->
|
||||||
|
<script>
|
||||||
|
import touch from '@/uni_modules/uv-ui-tools/libs/mixin/touch.js'
|
||||||
|
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
|
||||||
|
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
|
||||||
|
import props from './props.js';
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
import nvue from './nvue.js';
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ
|
||||||
|
import wxs from './wxs.js';
|
||||||
|
// #endif
|
||||||
|
/**
|
||||||
|
* SwipeActionItem 滑动单元格子组件
|
||||||
|
* @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作
|
||||||
|
* @tutorial https://www.uvui.cn/components/swipeAction.html
|
||||||
|
* @property {Boolean} show 控制打开或者关闭(默认 false )
|
||||||
|
* @property {String | Number} index 标识符,如果是v-for,可用index索引
|
||||||
|
* @property {Boolean} disabled 是否禁用(默认 false )
|
||||||
|
* @property {Boolean} autoClose 是否自动关闭其他swipe按钮组(默认 true )
|
||||||
|
* @property {Number} threshold 滑动距离阈值,只有大于此值,才被认为是要打开菜单(默认 30 )
|
||||||
|
* @property {Array} options 右侧按钮内容
|
||||||
|
* @property {String | Number} duration 动画过渡时间,单位ms(默认 350 )
|
||||||
|
* @event {Function(index)} open 组件打开时触发
|
||||||
|
* @event {Function(index)} close 组件关闭时触发
|
||||||
|
* @example <uv-swipe-action><uv-swipe-action-item :options="options1" ></uv-swipe-action-item></uv-swipe-action>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'uv-swipe-action-item',
|
||||||
|
emits: ['click'],
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
mixins: [mpMixin, mixin, props, touch],
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
mixins: [mpMixin, mixin, props, nvue , touch],
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ
|
||||||
|
mixins: [mpMixin, mixin, props, touch, wxs],
|
||||||
|
// #endif
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 按钮的尺寸信息
|
||||||
|
size: {},
|
||||||
|
// 父组件uv-swipe-action的参数
|
||||||
|
parentData: {
|
||||||
|
autoClose: true,
|
||||||
|
},
|
||||||
|
// 当前状态,open-打开,close-关闭
|
||||||
|
status: 'close',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 由于wxs无法直接读取外部的值,需要在外部值变化时,重新执行赋值逻辑
|
||||||
|
wxsInit(newValue, oldValue) {
|
||||||
|
this.queryRect()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
wxsInit() {
|
||||||
|
return [this.disabled, this.autoClose, this.threshold, this.options, this.duration]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// #ifdef MP-TOUTIAO
|
||||||
|
this.$uv.error('抖音小程序暂不支持wxs,故该组件暂不支持抖音小程序');
|
||||||
|
// #endif
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init() {
|
||||||
|
// 初始化父组件数据
|
||||||
|
this.updateParentData()
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.$uv.sleep().then(() => {
|
||||||
|
this.queryRect()
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
updateParentData() {
|
||||||
|
// 此方法在mixin中
|
||||||
|
this.getParentData('uv-swipe-action')
|
||||||
|
},
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
// 查询节点
|
||||||
|
queryRect() {
|
||||||
|
this.$uvGetRect('.uv-swipe-action-item__right__button', true).then(buttons => {
|
||||||
|
this.size = {
|
||||||
|
buttons,
|
||||||
|
show: this.show,
|
||||||
|
disabled: this.disabled,
|
||||||
|
threshold: this.threshold,
|
||||||
|
duration: this.duration
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
// 按钮被点击
|
||||||
|
buttonClickHandler(item, index) {
|
||||||
|
this.$emit('click', {
|
||||||
|
index,
|
||||||
|
name: this.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$show-lines: 1;
|
||||||
|
@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
|
||||||
|
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
|
||||||
|
|
||||||
|
.uv-swipe-action-item {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
/* #ifndef APP-NVUE || MP-WEIXIN */
|
||||||
|
touch-action: pan-y;
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__right {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
@include flex;
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
@include flex;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
@include flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 15px;
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
@include flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-size: 15px;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
// 关闭时执行
|
||||||
|
closeHandler() {
|
||||||
|
this.status = 'close'
|
||||||
|
},
|
||||||
|
setState(status) {
|
||||||
|
this.status = status
|
||||||
|
},
|
||||||
|
closeOther() {
|
||||||
|
// 尝试关闭其他打开的单元格
|
||||||
|
this.parent && this.parent.closeOther(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
// 是否自动关闭其他swipe按钮组
|
||||||
|
autoClose: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
...uni.$uv?.props?.swipeAction
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uv-swipe-action">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
|
||||||
|
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
|
||||||
|
import props from './props.js';
|
||||||
|
/**
|
||||||
|
* SwipeAction 滑动单元格
|
||||||
|
* @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作
|
||||||
|
* @tutorial https://www.uvui.cn/components/swipeAction.html
|
||||||
|
* @property {Boolean} autoClose 是否自动关闭其他swipe按钮组
|
||||||
|
* @event {Function(index)} click 点击组件时触发
|
||||||
|
* @example <uv-swipe-action><uv-swipe-action-item :rightOptions="options1" ></uv-swipe-action-item></uv-swipe-action>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'uv-swipe-action',
|
||||||
|
mixins: [mpMixin, mixin, props],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
swipeAction: this
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 这里computed的变量,都是子组件uv-swipe-action-item需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
|
||||||
|
// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(uv-swipe-action-item)
|
||||||
|
// 拉取父组件新的变化后的参数
|
||||||
|
parentData() {
|
||||||
|
return [this.autoClose]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
|
||||||
|
parentData() {
|
||||||
|
if (this.children.length) {
|
||||||
|
this.children.map(child => {
|
||||||
|
// 判断子组件(uv-swipe-action-item)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
|
||||||
|
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.children = []
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeOther(child) {
|
||||||
|
if (this.autoClose) {
|
||||||
|
// 历遍所有的单元格,找出非当前操作中的单元格,进行关闭
|
||||||
|
this.children.map((item, index) => {
|
||||||
|
if (child !== item) {
|
||||||
|
item.closeHandler()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"id": "uv-swipe-action",
|
||||||
|
"displayName": "uv-swipe-action 滑动单元格 全面兼容小程序、nvue、vue2、vue3等多端",
|
||||||
|
"version": "1.0.4",
|
||||||
|
"description": "滑动单元格组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作。",
|
||||||
|
"keywords": [
|
||||||
|
"uv-swipe-action",
|
||||||
|
"uvui",
|
||||||
|
"uv-ui",
|
||||||
|
"swipe-action",
|
||||||
|
"滑动单元格"
|
||||||
|
],
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.1.0"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"type": "component-vue",
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "插件不采集任何数据",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": ""
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [
|
||||||
|
"uv-ui-tools",
|
||||||
|
"uv-icon"
|
||||||
|
],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
},
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "y"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y",
|
||||||
|
"钉钉": "u",
|
||||||
|
"快手": "u",
|
||||||
|
"飞书": "u",
|
||||||
|
"京东": "u"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
## SwipeAction 滑动单元格
|
||||||
|
|
||||||
|
> **组件名:uv-swipe-action**
|
||||||
|
|
||||||
|
该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作。
|
||||||
|
|
||||||
|
### <a href="https://www.uvui.cn/components/swipeAction.html" target="_blank">查看文档</a>
|
||||||
|
|
||||||
|
### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
|
||||||
|
|
||||||
|
#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
## 1.0.6(2023-12-20)
|
||||||
|
1. 优化
|
||||||
|
## 1.0.5(2023-08-24)
|
||||||
|
1. 修复标题文字过多未隐藏掉的BUG
|
||||||
|
## 1.0.4(2023-07-24)
|
||||||
|
1. 增加 滑动方向是否为纵向 属性vertical
|
||||||
|
## 1.0.3(2023-06-27)
|
||||||
|
1. 增加titleStyle属性,方便修改标题样式
|
||||||
|
2. 标题上去掉是否是图片的判断,避免无后缀的图片不显示
|
||||||
|
## 1.0.2(2023-06-01)
|
||||||
|
1. 修复点击触发两次事件的BUG
|
||||||
|
## 1.0.1(2023-05-16)
|
||||||
|
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
|
||||||
|
2. 优化部分功能
|
||||||
|
## 1.0.0(2023-05-10)
|
||||||
|
uv-swiper 轮播图,走马灯
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
// 轮播的长度
|
||||||
|
length: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 当前处于活动状态的轮播的索引
|
||||||
|
current: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 指示器非激活颜色
|
||||||
|
indicatorActiveColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 指示器的激活颜色
|
||||||
|
indicatorInactiveColor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 指示器模式,line-线型,dot-点型
|
||||||
|
indicatorMode: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
...uni.$uv?.props?.swiperIndicator
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<template>
|
||||||
|
<view class="uv-swiper-indicator">
|
||||||
|
<view
|
||||||
|
class="uv-swiper-indicator__wrapper"
|
||||||
|
v-if="indicatorMode === 'line'"
|
||||||
|
:class="[`uv-swiper-indicator__wrapper--${indicatorMode}`]"
|
||||||
|
:style="{
|
||||||
|
width: $uv.addUnit(lineWidth * length),
|
||||||
|
backgroundColor: indicatorInactiveColor
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="uv-swiper-indicator__wrapper--line__bar"
|
||||||
|
:style="[lineStyle]"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="uv-swiper-indicator__wrapper"
|
||||||
|
v-if="indicatorMode === 'dot'"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="uv-swiper-indicator__wrapper__dot"
|
||||||
|
v-for="(item, index) in length"
|
||||||
|
:key="index"
|
||||||
|
:class="[index === current && 'uv-swiper-indicator__wrapper__dot--active']"
|
||||||
|
:style="[dotStyle(index)]"
|
||||||
|
>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
|
||||||
|
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
|
||||||
|
import props from './props.js';
|
||||||
|
/**
|
||||||
|
* SwiperIndicator 轮播图指示器
|
||||||
|
* @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用,
|
||||||
|
* @tutorial https://www.uvui.cn/components/swiper.html
|
||||||
|
* @property {String | Number} length 轮播的长度(默认 0 )
|
||||||
|
* @property {String | Number} current 当前处于活动状态的轮播的索引(默认 0 )
|
||||||
|
* @property {String} indicatorActiveColor 指示器非激活颜色
|
||||||
|
* @property {String} indicatorInactiveColor 指示器的激活颜色
|
||||||
|
* @property {String} indicatorMode 指示器模式(默认 'line' )
|
||||||
|
* @example <uv-swiper :list="list4" indicator keyName="url" :autoplay="false"></uv-swiper>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'uv-swiper-indicator',
|
||||||
|
mixins: [mpMixin, mixin, props],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
lineWidth: 22
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 指示器为线型的样式
|
||||||
|
lineStyle() {
|
||||||
|
let style = {}
|
||||||
|
style.width = this.$uv.addUnit(this.lineWidth)
|
||||||
|
style.transform = `translateX(${ this.$uv.addUnit(this.current * this.lineWidth) })`
|
||||||
|
style.backgroundColor = this.indicatorActiveColor
|
||||||
|
return style
|
||||||
|
},
|
||||||
|
// 指示器为点型的样式
|
||||||
|
dotStyle() {
|
||||||
|
return index => {
|
||||||
|
let style = {}
|
||||||
|
style.backgroundColor = index === this.current ? this.indicatorActiveColor : this.indicatorInactiveColor
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
|
||||||
|
|
||||||
|
.uv-swiper-indicator {
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
@include flex;
|
||||||
|
|
||||||
|
&--line {
|
||||||
|
border-radius: 100px;
|
||||||
|
height: 4px;
|
||||||
|
|
||||||
|
&__bar {
|
||||||
|
width: 22px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 100px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__dot {
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 100px;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
// 列表数组,元素可为字符串,如为对象可通过keyName指定目标属性名
|
||||||
|
list: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// 是否显示面板指示器
|
||||||
|
indicator: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 指示器非激活颜色
|
||||||
|
indicatorActiveColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff'
|
||||||
|
},
|
||||||
|
// 指示器的激活颜色
|
||||||
|
indicatorInactiveColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'rgba(255, 255, 255, 0.35)'
|
||||||
|
},
|
||||||
|
// 指示器样式,可通过bottom,left,right进行定位
|
||||||
|
indicatorStyle: {
|
||||||
|
type: [String, Object],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 指示器模式,line-线型,dot-点型
|
||||||
|
indicatorMode: {
|
||||||
|
type: String,
|
||||||
|
default: 'line'
|
||||||
|
},
|
||||||
|
// 是否自动切换
|
||||||
|
autoplay: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// 当前所在滑块的 index
|
||||||
|
current: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 当前所在滑块的 item-id ,不能与 current 被同时指定
|
||||||
|
currentItemId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
// 滑块自动切换时间间隔
|
||||||
|
interval: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 3000
|
||||||
|
},
|
||||||
|
// 滑块切换过程所需时间
|
||||||
|
duration: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 300
|
||||||
|
},
|
||||||
|
// 播放到末尾后是否重新回到开头
|
||||||
|
circular: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 滑动方向是否为纵向
|
||||||
|
vertical: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 前边距,可用于露出前一项的一小部分,nvue和支付宝不支持
|
||||||
|
previousMargin: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 后边距,可用于露出后一项的一小部分,nvue和支付宝不支持
|
||||||
|
nextMargin: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
// 当开启时,会根据滑动速度,连续滑动多屏,支付宝不支持
|
||||||
|
acceleration: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 同时显示的滑块数量,nvue、支付宝小程序不支持
|
||||||
|
displayMultipleItems: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
// 指定swiper切换缓动动画类型,有效值:default、linear、easeInCubic、easeOutCubic、easeInOutCubic
|
||||||
|
// 只对微信小程序有效
|
||||||
|
easingFunction: {
|
||||||
|
type: String,
|
||||||
|
default: 'default'
|
||||||
|
},
|
||||||
|
// list数组中指定对象的目标属性名
|
||||||
|
keyName: {
|
||||||
|
type: String,
|
||||||
|
default: 'url'
|
||||||
|
},
|
||||||
|
// 图片的裁剪模式
|
||||||
|
imgMode: {
|
||||||
|
type: String,
|
||||||
|
default: 'aspectFill'
|
||||||
|
},
|
||||||
|
// 组件高度
|
||||||
|
height: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 130
|
||||||
|
},
|
||||||
|
// 背景颜色
|
||||||
|
bgColor: {
|
||||||
|
type: String,
|
||||||
|
default: '#f3f4f6'
|
||||||
|
},
|
||||||
|
// 组件圆角,数值或带单位的字符串
|
||||||
|
radius: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 4
|
||||||
|
},
|
||||||
|
// 是否加载中
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 是否显示标题,要求数组对象中有title属性
|
||||||
|
showTitle: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 显示的标题样式
|
||||||
|
titleStyle: {
|
||||||
|
type: [Object, String],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
...uni.$uv?.props?.swiper
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
<template>
|
||||||
|
<view
|
||||||
|
class="uv-swiper"
|
||||||
|
:style="{
|
||||||
|
backgroundColor: bgColor,
|
||||||
|
height: $uv.addUnit(height),
|
||||||
|
borderRadius: $uv.addUnit(radius)
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<view class="uv-swiper__loading" v-if="loading">
|
||||||
|
<uv-loading-icon mode="circle"></uv-loading-icon>
|
||||||
|
</view>
|
||||||
|
<swiper
|
||||||
|
v-else
|
||||||
|
class="uv-swiper__wrapper"
|
||||||
|
:style="{
|
||||||
|
height: $uv.addUnit(height),
|
||||||
|
flex: 1
|
||||||
|
}"
|
||||||
|
@change="change"
|
||||||
|
@transition="transition"
|
||||||
|
@animationfinish="animationfinish"
|
||||||
|
:circular="circular"
|
||||||
|
:vertical="vertical"
|
||||||
|
:interval="interval"
|
||||||
|
:duration="duration"
|
||||||
|
:autoplay="autoplay"
|
||||||
|
:current="current"
|
||||||
|
:currentItemId="currentItemId"
|
||||||
|
:previousMargin="$uv.addUnit(previousMargin)"
|
||||||
|
:nextMargin="$uv.addUnit(nextMargin)"
|
||||||
|
:acceleration="acceleration"
|
||||||
|
:displayMultipleItems="displayMultipleItems"
|
||||||
|
:easingFunction="easingFunction"
|
||||||
|
>
|
||||||
|
<swiper-item class="uv-swiper__wrapper__item" v-for="(item, index) in list" :key="index">
|
||||||
|
<view class="uv-swiper__wrapper__item__wrapper" :style="[itemStyle(index)]">
|
||||||
|
<!-- 在nvue中,image图片的宽度默认为屏幕宽度,需要通过flex:1撑开,另外必须设置高度才能显示图片 -->
|
||||||
|
<image
|
||||||
|
class="uv-swiper__wrapper__item__wrapper__image"
|
||||||
|
v-if="getItemType(item) === 'image'"
|
||||||
|
:src="getSource(item)"
|
||||||
|
:mode="imgMode"
|
||||||
|
@tap="clickHandler(index)"
|
||||||
|
:style="{
|
||||||
|
height: $uv.addUnit(height),
|
||||||
|
borderRadius: $uv.addUnit(radius)
|
||||||
|
}"
|
||||||
|
></image>
|
||||||
|
<video
|
||||||
|
class="uv-swiper__wrapper__item__wrapper__video"
|
||||||
|
v-if="getItemType(item) === 'video'"
|
||||||
|
:id="`video-${index}`"
|
||||||
|
:enable-progress-gesture="false"
|
||||||
|
:src="getSource(item)"
|
||||||
|
:poster="getPoster(item)"
|
||||||
|
:title="showTitle && $uv.test.object(item) && item.title ? item.title : ''"
|
||||||
|
:style="{
|
||||||
|
height: $uv.addUnit(height)
|
||||||
|
}"
|
||||||
|
controls
|
||||||
|
@tap="clickHandler(index)"
|
||||||
|
></video>
|
||||||
|
<text v-if="showTitle && $uv.test.object(item) && item.title" class="uv-swiper__wrapper__item__wrapper__title uv-line-1" :style="[$uv.addStyle(titleStyle)]">
|
||||||
|
{{ item.title }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
<view class="uv-swiper__indicator" :style="[$uv.addStyle(indicatorStyle)]">
|
||||||
|
<slot name="indicator">
|
||||||
|
<uv-swiper-indicator
|
||||||
|
v-if="!loading && indicator && !showTitle"
|
||||||
|
:indicatorActiveColor="indicatorActiveColor"
|
||||||
|
:indicatorInactiveColor="indicatorInactiveColor"
|
||||||
|
:length="list.length"
|
||||||
|
:current="currentIndex"
|
||||||
|
:indicatorMode="indicatorMode"
|
||||||
|
></uv-swiper-indicator>
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';
|
||||||
|
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';
|
||||||
|
import props from './props.js';
|
||||||
|
/**
|
||||||
|
* Swiper 轮播图
|
||||||
|
* @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用,
|
||||||
|
* @tutorial https://www.uvui.cn/components/swiper.html
|
||||||
|
* @property {Array} list 轮播图数据
|
||||||
|
* @property {Boolean} indicator 是否显示面板指示器(默认 false )
|
||||||
|
* @property {String} indicatorActiveColor 指示器非激活颜色(默认 '#FFFFFF' )
|
||||||
|
* @property {String} indicatorInactiveColor 指示器的激活颜色(默认 'rgba(255, 255, 255, 0.35)' )
|
||||||
|
* @property {String | Object} indicatorStyle 指示器样式,可通过bottom,left,right进行定位
|
||||||
|
* @property {String} indicatorMode 指示器模式(默认 'line' )
|
||||||
|
* @property {Boolean} autoplay 是否自动切换(默认 true )
|
||||||
|
* @property {String | Number} current 当前所在滑块的 index(默认 0 )
|
||||||
|
* @property {String} currentItemId 当前所在滑块的 item-id ,不能与 current 被同时指定
|
||||||
|
* @property {String | Number} interval 滑块自动切换时间间隔(ms)(默认 3000 )
|
||||||
|
* @property {String | Number} duration 滑块切换过程所需时间(ms)(默认 300 )
|
||||||
|
* @property {Boolean} circular 播放到末尾后是否重新回到开头(默认 false )
|
||||||
|
* @property {String | Number} previousMargin 前边距,可用于露出前一项的一小部分,nvue和支付宝不支持(默认 0 )
|
||||||
|
* @property {String | Number} nextMargin 后边距,可用于露出后一项的一小部分,nvue和支付宝不支持(默认 0 )
|
||||||
|
* @property {Boolean} acceleration 当开启时,会根据滑动速度,连续滑动多屏,支付宝不支持(默认 false )
|
||||||
|
* @property {Number} displayMultipleItems 同时显示的滑块数量,nvue、支付宝小程序不支持(默认 1 )
|
||||||
|
* @property {String} easingFunction 指定swiper切换缓动动画类型, 只对微信小程序有效(默认 'default' )
|
||||||
|
* @property {String} keyName list数组中指定对象的目标属性名(默认 'url' )
|
||||||
|
* @property {String} imgMode 图片的裁剪模式(默认 'aspectFill' )
|
||||||
|
* @property {String | Number} height 组件高度(默认 130 )
|
||||||
|
* @property {String} bgColor 背景颜色(默认 '#f3f4f6' )
|
||||||
|
* @property {String | Number} radius 组件圆角,数值或带单位的字符串(默认 4 )
|
||||||
|
* @property {Boolean} loading 是否加载中(默认 false )
|
||||||
|
* @property {Boolean} showTitle 是否显示标题,要求数组对象中有title属性(默认 false )
|
||||||
|
* @event {Function(index)} click 点击轮播图时触发 index:点击了第几张图片,从0开始
|
||||||
|
* @event {Function(index)} change 轮播图切换时触发(自动或者手动切换) index:切换到了第几张图片,从0开始
|
||||||
|
* @example <uv-swiper :list="list4" keyName="url" :autoplay="false"></uv-swiper>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'uv-swiper',
|
||||||
|
mixins: [mpMixin, mixin, props],
|
||||||
|
emits: ['click', 'change'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentIndex: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
current(val, preVal) {
|
||||||
|
if (val === preVal) return;
|
||||||
|
this.currentIndex = val; // 和上游数据关联上
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
itemStyle() {
|
||||||
|
return (index) => {
|
||||||
|
const style = {};
|
||||||
|
// #ifndef APP-NVUE || MP-TOUTIAO
|
||||||
|
// 左右流出空间的写法不支持nvue和头条
|
||||||
|
// 只有配置了此二值,才加上对应的圆角,以及缩放
|
||||||
|
if (this.nextMargin && this.previousMargin) {
|
||||||
|
style.borderRadius = this.$uv.addUnit(this.radius);
|
||||||
|
if (index !== this.currentIndex) style.transform = 'scale(0.92)';
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
return style;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getItemType(item) {
|
||||||
|
if (typeof item === 'string') return this.$uv.test.video(this.getSource(item)) ? 'video' : 'image';
|
||||||
|
if (typeof item === 'object' && this.keyName) {
|
||||||
|
if (!item.type) return this.$uv.test.video(this.getSource(item)) ? 'video' : 'image';
|
||||||
|
if (item.type === 'image') return 'image';
|
||||||
|
if (item.type === 'video') return 'video';
|
||||||
|
return 'image';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 获取目标路径,可能数组中为字符串,对象的形式,额外可指定对象的目标属性名keyName
|
||||||
|
getSource(item) {
|
||||||
|
if (typeof item === 'string') return item;
|
||||||
|
if (typeof item === 'object' && this.keyName) return item[this.keyName];
|
||||||
|
else this.$uv.error('请按格式传递列表参数');
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
// 轮播切换事件
|
||||||
|
change(e) {
|
||||||
|
// 当前的激活索引
|
||||||
|
const { current } = e.detail;
|
||||||
|
this.pauseVideo(this.currentIndex);
|
||||||
|
this.currentIndex = current;
|
||||||
|
this.$emit('change', e.detail);
|
||||||
|
},
|
||||||
|
transition(e) {
|
||||||
|
this.$emit('transition', e.detail);
|
||||||
|
},
|
||||||
|
animationfinish(e) {
|
||||||
|
this.$emit('animationfinish', e.detail);
|
||||||
|
},
|
||||||
|
// 切换轮播时,暂停视频播放
|
||||||
|
pauseVideo(index) {
|
||||||
|
const lastItem = this.getSource(this.list[index]);
|
||||||
|
if (this.$uv.test.video(lastItem)) {
|
||||||
|
// 当视频隐藏时,暂停播放
|
||||||
|
const video = uni.createVideoContext(`video-${index}`, this);
|
||||||
|
video.pause();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 当一个轮播item为视频时,获取它的视频海报
|
||||||
|
getPoster(item) {
|
||||||
|
return typeof item === 'object' && item.poster ? item.poster : '';
|
||||||
|
},
|
||||||
|
// 点击某个item
|
||||||
|
clickHandler(index) {
|
||||||
|
this.$emit('click', index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$show-lines: 1;
|
||||||
|
@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
|
||||||
|
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
|
||||||
|
.uv-swiper {
|
||||||
|
@include flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
@include flex;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__video {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
font-size: 28rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
line-height: 60rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"id": "uv-swiper",
|
||||||
|
"displayName": "uv-swiper 轮播图 全面兼容vue3+2、app、h5、小程序等多端",
|
||||||
|
"version": "1.0.6",
|
||||||
|
"description": "该组件为轮播、跑马灯,支持卡片式。一般用于导航轮播,广告展示等场景,开箱即用",
|
||||||
|
"keywords": [
|
||||||
|
"swiper",
|
||||||
|
"uvui",
|
||||||
|
"uv-ui",
|
||||||
|
"轮播图",
|
||||||
|
"走马灯"
|
||||||
|
],
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.1.0"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"type": "component-vue",
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "插件不采集任何数据",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": ""
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [
|
||||||
|
"uv-ui-tools",
|
||||||
|
"uv-loading-icon"
|
||||||
|
],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"cloud": {
|
||||||
|
"tcb": "y",
|
||||||
|
"aliyun": "y"
|
||||||
|
},
|
||||||
|
"client": {
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
},
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "y"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "y"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "y",
|
||||||
|
"IE": "y",
|
||||||
|
"Edge": "y",
|
||||||
|
"Firefox": "y",
|
||||||
|
"Safari": "y"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "y",
|
||||||
|
"百度": "y",
|
||||||
|
"字节跳动": "y",
|
||||||
|
"QQ": "y",
|
||||||
|
"钉钉": "u",
|
||||||
|
"快手": "u",
|
||||||
|
"飞书": "u",
|
||||||
|
"京东": "u"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "u",
|
||||||
|
"联盟": "u"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
## Swiper 轮播图
|
||||||
|
|
||||||
|
> **组件名:uv-swiper**
|
||||||
|
|
||||||
|
该组件为轮播、跑马灯,支持卡片式。一般用于导航轮播,广告展示等场景,开箱即用。
|
||||||
|
|
||||||
|
# <a href="https://www.uvui.cn/components/swiper.html" target="_blank">查看文档</a>
|
||||||
|
|
||||||
|
## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
|
||||||
|
|
||||||
|
### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
|
||||||
|
|
||||||
|
<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</a>
|
||||||
|
|
||||||
|
#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
|
||||||