Browse Source

feat: page-live;

pull/2/head
Fox-33 5 months ago
parent
commit
cc6e273316
22 changed files with 2133 additions and 52 deletions
  1. +20
    -18
      components/home/pictureLiveView.vue
  2. +1
    -1
      components/product/productCard.vue
  3. +9
    -0
      pages.json
  4. +1
    -1
      pages/index/index.vue
  5. +165
    -0
      pages_order/article/search.vue
  6. +1
    -1
      pages_order/comment/recordFormPopup.vue
  7. +0
    -0
      pages_order/components/reloateProjectPopup.vue
  8. +8
    -1
      pages_order/growing/activity/index.vue
  9. +120
    -0
      pages_order/growing/activity/markList.vue
  10. +34
    -10
      pages_order/growing/activity/markPopup.vue
  11. +262
    -0
      pages_order/growing/activity/reviewPopup.vue
  12. +13
    -10
      pages_order/growing/activity/search.vue
  13. +361
    -0
      pages_order/live/formPopup.vue
  14. +241
    -0
      pages_order/live/index.vue
  15. +235
    -0
      pages_order/live/list.vue
  16. +13
    -10
      pages_order/product/search.vue
  17. BIN
      static/image/icon-location.png
  18. +4
    -0
      uni_modules/SY-StackedCarousel/changelog.md
  19. +421
    -0
      uni_modules/SY-StackedCarousel/components/SY-StackedCarousel/SY-StackedCarousel.vue
  20. +22
    -0
      uni_modules/SY-StackedCarousel/components/SY-StackedCarousel/util.js
  21. +85
    -0
      uni_modules/SY-StackedCarousel/package.json
  22. +117
    -0
      uni_modules/SY-StackedCarousel/readme.md

+ 20
- 18
components/home/pictureLiveView.vue View File

@ -17,24 +17,26 @@
</view>
</view>
</view> -->
<swiper
class="swiper"
:current="current"
:autoplay="true"
:display-multiple-items="3.2"
>
<swiper-item v-for="item in liveList" :key="item.id" style="display: inline-block;">
<view class="swiper-item">
<view class="swiper-item-content" @click="jumpToLive(item.id)">
<image class="live-item-bg" :src="item.image" mode="aspectFill"></image>
<view class="live-item-info">
<view class="text-ellipsis live-item-info-title">{{ item.title }}</view>
<view class="live-item-info-time">{{ item.time }}</view>
<view class="live-content">
<swiper
class="swiper"
:current="current"
:autoplay="true"
:display-multiple-items="3.2"
>
<swiper-item v-for="item in liveList" :key="item.id" style="display: inline-block;">
<view class="swiper-item">
<view class="swiper-item-content" @click="jumpToLive(item.id)">
<image class="live-item-bg" :src="item.image" mode="aspectFill"></image>
<view class="live-item-info">
<view class="text-ellipsis live-item-info-title">{{ item.title }}</view>
<view class="live-item-info-time">{{ item.time }}</view>
</view>
</view>
</view>
</view>
</swiper-item>
</swiper>
</swiper-item>
</swiper>
</view>
</view>
</template>
@ -94,10 +96,10 @@
},
jumpToLive(id) {
// todo
this.$utils.navigateTo(`/pages_order/live/index?id=${id}`)
},
showAll() {
// todo
this.$utils.navigateTo(`/pages_order/live/list`)
}
},
}


+ 1
- 1
components/product/productCard.vue View File

@ -72,7 +72,7 @@
const width = this.isCollected ? 80 : 56
const background = this.isCollected ? '#26334E' : '#FF9035'
let display = Math.ceil(this.displayX / this.collectBtnWidth * 100)
let display = Math.ceil(this.displayX / width * 100)
display > 100 && (display = 100)


+ 9
- 0
pages.json View File

@ -103,6 +103,9 @@
{
"path": "growing/activity/index"
},
{
"path": "growing/activity/markList"
},
{
"path": "growing/activity/applyEmail"
},
@ -118,6 +121,12 @@
{
"path": "member/switch"
},
{
"path": "live/list"
},
{
"path": "live/index"
},
{
"path": "partner/apply"
},


+ 1
- 1
pages/index/index.vue View File

@ -93,7 +93,7 @@
}
},
onLoad() {
// this.$utils.navigateTo(`/pages_order/growing/activity/applyEmail`)
// this.$utils.navigateTo(`/pages_order/live/index`)
// uni.navigateTo({
// url: `/pages_order/order/orderConfirm/index`
// })


+ 165
- 0
pages_order/article/search.vue View File

@ -0,0 +1,165 @@
<template>
<view class="page__view highlight">
<!-- 导航栏 -->
<navbar :title="title" leftClick @leftClick="$utils.navigateBack" bgColor="transparent" color="#191919" />
<!-- 搜索栏 -->
<view :class="['flex', 'search', isFocusSearch ? 'is-focus' : '']" >
<uv-search
v-model="keyword"
placeholder="请输入要查询的内容"
color="#181818"
bgColor="transparent"
:showAction="isFocusSearch"
@custom="search"
@search="search"
@focus="isFocusSearch = true"
@blur="isFocusSearch = false"
>
<template #prefix>
<image class="search-icon" src="/static/image/icon-search-dark.png" mode="widthFix"></image>
</template>
</uv-search>
</view>
<view v-if="list.length" class="list">
<view class="list-item" v-for="item in list" :key="item.id">
<view class="cover">
<image class="img" :src="item.image" mode="aspectFill"></image>
</view>
<view class="info">
<view class="title text-ellipsis-2">{{ item.title }}</view>
<view class="desc">{{ item.createTime }}</view>
</view>
</view>
</view>
<template v-else>
<uv-empty mode="list"></uv-empty>
</template>
</view>
</template>
<script>
import mixinsList from '@/mixins/list.js'
import sortBar from './sortBar.vue'
import recordsView from '@/components/growing/recordsView.vue'
export default {
mixins: [mixinsList],
components: {
sortBar,
recordsView,
},
data() {
return {
title: '搜索',
keyword: '',
isFocusSearch: false,
queryParams: {
pageNo: 1,
pageSize: 10,
title: '',
},
// todo
mixinsListApi: '',
}
},
onLoad({ title, api }) {
this.title = title
this.mixinsListApi = api
this.getData()
},
methods: {
search() {
this.queryParams.pageNo = 1
this.queryParams.pageSize = 10
this.queryParams.title = this.keyword
this.getData()
},
},
}
</script>
<style scoped lang="scss">
.search {
$h: 64rpx;
$radius: 32rpx;
$borderWidth: 4rpx;
margin: 24rpx 32rpx 0 32rpx;
width: calc(100% - 32rpx * 2);
height: $h;
position: relative;
border-radius: $radius;
&-icon {
margin: 0 13rpx 0 26rpx;
width: 30rpx;
height: auto;
}
&.is-focus {
/deep/ .uv-search__action {
padding: 19rpx 24rpx;
font-size: 26rpx;
font-weight: 500;
line-height: 1;
color: #FFFFFF;
background: #00A9FF;
border-radius: 32rpx;
}
}
}
.list {
margin-top: 24rpx;
padding: 12rpx 40rpx;
&-item {
column-gap: 16rpx;
& + & {
margin-top: 40rpx;
padding: 24rpx;
background: #F9F9F9;
border: 2rpx solid #FFFFFF;
border-radius: 24rpx;
}
.cover {
width: 152rpx;
height: 152rpx;
border-radius: 8rpx;
overflow: hidden;
border: 2rpx solid #E6E6E6;
.img {
width: 100%;
height: 100%;
}
}
.info {
flex: 1;
.title {
font-size: 28rpx;
font-weight: 500;
color: #000000;
}
.desc {
margin-top: 8rpx;
font-size: 24rpx;
color: #8B8B8B;
}
}
}
}
</style>

+ 1
- 1
pages_order/comment/recordFormPopup.vue View File

@ -89,7 +89,7 @@
</template>
<script>
import reloateProjectPopup from './reloateProjectPopup.vue'
import reloateProjectPopup from '@/pages_order/components/reloateProjectPopup.vue'
import formTextarea from '@/pages_order/components/formTextarea.vue'
import formUpload from '@/pages_order/components/formUpload.vue'
import formRate from '@/pages_order/components/formRate.vue'


pages_order/comment/reloateProjectPopup.vue → pages_order/components/reloateProjectPopup.vue View File


+ 8
- 1
pages_order/growing/activity/index.vue View File

@ -44,7 +44,7 @@
</view>
<view>活动掠影</view>
</view>
<view class="btn btn-mark">标记有我</view>
<view class="btn btn-mark" @click="onMark">标记有我</view>
</view>
<view class="section-content highlights">
<view class="highlights-item" v-for="(image, idx) in detail.highlights" :key="idx">
@ -72,6 +72,8 @@
</view>
</scroll-view>
<markPopup ref="markPopup"></markPopup>
<view class="flex bottom">
<button class="flex btn btn-palin" @click="jumpToPoster">生成海报</button>
@ -82,10 +84,12 @@
<script>
import commentCard from '@/pages_order/comment/commentCard.vue'
import markPopup from '@/pages_order/growing/activity/markPopup.vue'
export default {
components: {
commentCard,
markPopup,
},
data() {
return {
@ -139,6 +143,9 @@
onApplyEmail() {
this.$utils.navigateTo(`/pages_order/growing/activity/applyEmail?id=${this.id}`)
},
onMark() {
this.$refs.markPopup.open(this.id)
},
},
}
</script>


+ 120
- 0
pages_order/growing/activity/markList.vue View File

@ -0,0 +1,120 @@
<template>
<view class="page__view highlight">
<navbar title="标记有我" leftClick @leftClick="$utils.navigateBack" />
<view class="main">
<view class="section" v-for="item in list" :key="item.id">
<view class="section-header">
<view class="flex title">{{ item.title }}</view>
<button class="flex btn btn-all" @click="jumpToGrowing(item.id)">
<view>查看成长档案</view>
<image class="icon" src="@/static/image/icon-arrow-right.png" mode="widthFix"></image>
</button>
</view>
<view class="section-content record">
<view class="record-item" v-for="(image, imgIdx) in item.images" :key="imgIdx">
<image class="img" :src="image" mode="scaleToFill"></image>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import mixinsList from '@/mixins/list.js'
export default {
mixins: [mixinsList],
data() {
return {
// todo
mixinsListApi: '',
}
},
methods: {
jumpToGrowing(id) {
// todo
this.$utils.navigateTo(`/pages/index/growing`)
},
},
}
</script>
<style lang="scss" scoped>
.swiper {
margin-top: 40rpx;
.title {
margin-top: 12rpx;
font-size: 28rpx;
font-weight: 600;
color: #252545;
}
.tag {
margin-top: 4rpx;
padding: 2rpx 8rpx;
font-size: 24rpx;
color: #00A9FF;
background: #E0F5FF;
border-radius: 8rpx;
}
}
.main {
padding: 0 40rpx;
}
.section {
margin-top: 64rpx;
&-header {
font-size: 36rpx;
font-weight: 500;
color: #191919;
.title {
flex: 1;
justify-content: flex-start;
column-gap: 8rpx;
}
.btn-all {
column-gap: 4rpx;
font-size: 24rpx;
line-height: 1.4;
color: #8B8B8B;
.icon {
width: 32rpx;
height: auto;
}
}
}
&-content {
margin-top: 24rpx;
}
}
.record {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
&-item {
border: 2rpx solid #CDCDCD;
border-radius: 12rpx;
overflow: hidden;
.img {
width: 100%;
height: 100%;
}
}
}
</style>

+ 34
- 10
pages_order/growing/activity/markPopup.vue View File

@ -3,7 +3,7 @@
<uv-popup ref="popup" mode="bottom" bgColor="none" >
<view class="popup__view">
<view class="flex header">
<view class="title">新增回顾</view>
<view class="title">标记有我</view>
<button class="btn" @click="close">关闭</button>
</view>
<view class="form">
@ -17,7 +17,7 @@
<uv-form-item prop="image" :customStyle="formItemStyle">
<view class="form-item-label">
<image class="icon" src="@/static/image/icon-require.png" mode="widthFix"></image>
上传
上传本人照
</view>
<view class="form-item-content">
<button class="flex btn">
@ -36,8 +36,9 @@
</view>
</uv-form>
</view>
<view class="footer">
<button class="flex btn" @click="onPublish">发布</button>
<view class="flex footer">
<button class="flex btn btn-palin" @click="jumpToGrowing">成长档案</button>
<button class="flex btn btn-primary" @click="onSubmit">开始标记</button>
</view>
</view>
</uv-popup>
@ -48,6 +49,7 @@
export default {
data() {
return {
id: null,
form: {
image: null,
},
@ -62,6 +64,13 @@
}
},
methods: {
open(id) {
this.id = id
this.$refs.popup.open();
},
close() {
this.$refs.popup.close();
},
onUpload() {
uni.chooseImage({
count: 1,
@ -76,7 +85,7 @@
}
});
},
async onPublish() {
async onSubmit() {
try {
await this.$refs.form.validate()
@ -91,17 +100,23 @@
uni.showToast({
icon: 'success',
title: '发布成功',
title: '提交成功',
});
this.$emit('submitted')
this.close()
this.$utils.navigateTo(`/pages_order/growing/activity/markList`)
} catch (err) {
console.log('onSave err', err)
}
},
jumpToGrowing(id) {
// todo
this.$utils.navigateTo(`/pages/index/growing`)
},
},
}
</script>
@ -196,23 +211,32 @@
}
.footer {
column-gap: 32rpx;
width: 100%;
padding: 32rpx 40rpx;
box-sizing: border-box;
border-top: 2rpx solid #F1F1F1;
.btn {
width: 100%;
flex: 1;
padding: 14rpx 0;
box-sizing: border-box;
font-family: PingFang SC;
font-weight: 500;
font-size: 36rpx;
line-height: 1.4;
color: #FFFFFF;
background-image: linear-gradient(to right, #21FEEC, #019AF9);
border: 2rpx solid #00A9FF;
border-radius: 41rpx;
&-palin {
color: #252545;
border: 2rpx solid #252545;
}
&-primary {
color: #FFFFFF;
background: linear-gradient(to right, #21FEEC, #019AF9);
border: 2rpx solid #00A9FF;
}
}
}


+ 262
- 0
pages_order/growing/activity/reviewPopup.vue View File

@ -0,0 +1,262 @@
<template>
<view>
<uv-popup ref="popup" mode="bottom" bgColor="none" >
<view class="popup__view">
<view class="flex header">
<view class="title">新增回顾</view>
<button class="btn" @click="close">关闭</button>
</view>
<view class="form">
<uv-form
ref="form"
:model="form"
:rules="rules"
errorType="toast"
>
<view class="form-item">
<uv-form-item prop="image" :customStyle="formItemStyle">
<view class="form-item-label">
<image class="icon" src="@/static/image/icon-require.png" mode="widthFix"></image>
上传图片
</view>
<view class="form-item-content">
<button class="flex btn">
<view v-if="form.image" class="avatar">
<image class="img" :src="form.image" mode="aspectFill"></image>
<view class="flex mask">
<image class="icon" src="@/static/image/icon-change.png" mode="widthFix" />
</view>
</view>
<view v-else class="flex avatar is-empty">
<image class="icon" src="@/static/image/icon-plus.png" mode="widthFix" />
</view>
</button>
</view>
</uv-form-item>
</view>
</uv-form>
</view>
<view class="footer">
<button class="flex btn" @click="onPublish">发布</button>
</view>
</view>
</uv-popup>
</view>
</template>
<script>
export default {
data() {
return {
form: {
image: null,
},
rules: {
'image': {
type: 'string',
required: true,
message: '请上传图片',
},
},
formItemStyle: { padding: 0 },
}
},
methods: {
onUpload() {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'], //
success: res => {
let image = res.tempFilePaths[0] // cover
this.$Oss.ossUpload(image)
.then(url => {
this.form.image = url
})
}
});
},
async onPublish() {
try {
await this.$refs.form.validate()
const {
} = this.form
const params = {
}
// todo: fetch
// await this.$fetch('updateAddress', params)
uni.showToast({
icon: 'success',
title: '发布成功',
});
this.$emit('submitted')
this.close()
} catch (err) {
console.log('onSave err', err)
}
},
},
}
</script>
<style lang="scss" scoped>
.popup__view {
width: 100vw;
display: flex;
flex-direction: column;
box-sizing: border-box;
background: #FFFFFF;
border-top-left-radius: 32rpx;
border-top-right-radius: 32rpx;
}
.header {
position: relative;
width: 100%;
padding: 24rpx 0;
box-sizing: border-box;
border-bottom: 2rpx solid #EEEEEE;
.title {
font-family: PingFang SC;
font-weight: 500;
font-size: 34rpx;
line-height: 1.4;
color: #181818;
}
.btn {
font-family: PingFang SC;
font-weight: 500;
font-size: 32rpx;
line-height: 1.4;
color: #8B8B8B;
position: absolute;
top: 26rpx;
left: 40rpx;
}
}
.form {
max-height: 75vh;
padding: 32rpx 40rpx;
box-sizing: border-box;
overflow-y: auto;
&-item {
padding: 8rpx 0 6rpx 0;
& + & {
padding-top: 24rpx;
border-top: 2rpx solid #EEEEEE;
}
&-label {
margin-bottom: 14rpx;
display: flex;
align-items: center;
font-family: PingFang SC;
font-weight: 400;
font-size: 26rpx;
line-height: 1.4;
color: #181818;
.icon {
margin-right: 8rpx;
width: 16rpx;
height: auto;
}
}
&-content {
.text {
padding: 2rpx 0;
font-family: PingFang SC;
font-weight: 400;
font-size: 32rpx;
line-height: 1.4;
&.placeholder {
color: #C6C6C6;
}
}
}
}
}
.footer {
width: 100%;
padding: 32rpx 40rpx;
box-sizing: border-box;
border-top: 2rpx solid #F1F1F1;
.btn {
width: 100%;
padding: 14rpx 0;
box-sizing: border-box;
font-family: PingFang SC;
font-weight: 500;
font-size: 36rpx;
line-height: 1.4;
color: #FFFFFF;
background-image: linear-gradient(to right, #21FEEC, #019AF9);
border: 2rpx solid #00A9FF;
border-radius: 41rpx;
}
}
.btn-avatar {
display: inline-block;
width: auto;
border: none;
}
.avatar {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 24rpx;
overflow: hidden;
.img {
width: 100%;
height: 100%;
}
.mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #00000080;
border-radius: 24rpx;
.icon {
width: 64rpx;
height: 64rpx;
}
}
&.is-empty {
background: #F3F2F7;
.icon {
width: 61rpx;
height: auto;
}
}
}
</style>

+ 13
- 10
pages_order/growing/activity/search.vue View File

@ -5,13 +5,13 @@
<navbar title="搜索结果" leftClick @leftClick="$utils.navigateBack" bgColor="transparent" color="#191919" />
<!-- 搜索栏 -->
<view class="flex search">
<view :class="['flex', 'search', isFocusSearch ? 'is-focus' : '']" >
<uv-search
v-model="keyword"
placeholder="输入关键词搜索"
color="#181818"
bgColor="transparent"
:showAction="true"
:showAction="isFocusSearch"
@custom="search"
@search="search"
@focus="isFocusSearch = true"
@ -52,6 +52,7 @@
data() {
return {
keyword: '',
isFocusSearch: false,
queryParams: {
pageNo: 1,
pageSize: 10,
@ -145,14 +146,16 @@
border: 4rpx solid transparent;
}
/deep/ .uv-search__action {
padding: 19rpx 24rpx;
font-size: 26rpx;
font-weight: 500;
line-height: 1;
color: #FFFFFF;
background: #00A9FF;
border-radius: 32rpx;
&.is-focus {
/deep/ .uv-search__action {
padding: 19rpx 24rpx;
font-size: 26rpx;
font-weight: 500;
line-height: 1;
color: #FFFFFF;
background: #00A9FF;
border-radius: 32rpx;
}
}
}


+ 361
- 0
pages_order/live/formPopup.vue View File

@ -0,0 +1,361 @@
<template>
<view>
<uv-popup ref="popup" mode="bottom" bgColor="none" >
<view class="popup__view">
<view class="flex header">
<view class="title">新增记录</view>
<button class="btn" @click="close">关闭</button>
</view>
<view class="form">
<uv-form
ref="form"
:model="form"
:rules="rules"
errorType="toast"
>
<view class="form-item">
<uv-form-item prop="project" :customStyle="formItemStyle">
<view class="form-item-label">
<image class="icon" src="@/static/image/icon-require.png" mode="widthFix"></image>
关联项目
</view>
<view class="form-item-content">
<view class="flex row" @click="openRelatePojectPicker">
<view v-if="form.project" class="text">{{ projectDesc }}</view>
<view v-else class="text placeholder">请选择关联项目</view>
<uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon>
</view>
<reloateProjectPopup ref="reloateProjectPopup" :options="projects" @confirm="onRelateProjectChange"></reloateProjectPopup>
</view>
</uv-form-item>
</view>
<view class="form-item">
<uv-form-item prop="area" :customStyle="formItemStyle">
<view class="form-item-label">选择地址</view>
<view class="form-item-content">
<view class="flex row" @click="selectAddr">
<view v-if="form.area" class="text">{{ form.area }}</view>
<view v-else class="text placeholder">请选择地址</view>
<uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon>
</view>
</view>
</uv-form-item>
</view>
<view class="form-item">
<uv-form-item prop="images" :customStyle="formItemStyle">
<view class="form-item-label">上传图片</view>
<view class="form-item-content">
<formUpload v-model="form.images"></formUpload>
</view>
</uv-form-item>
</view>
</uv-form>
</view>
<view class="footer">
<button class="flex btn" @click="onPublish">发布</button>
</view>
</view>
</uv-popup>
</view>
</template>
<script>
import Position from '@/utils/position.js'
import reloateProjectPopup from '@/pages_order/components/reloateProjectPopup.vue'
import formTextarea from '@/pages_order/components/formTextarea.vue'
import formUpload from '@/pages_order/components/formUpload.vue'
import formRate from '@/pages_order/components/formRate.vue'
export default {
components: {
reloateProjectPopup,
formTextarea,
formUpload,
formRate,
},
data() {
return {
form: {
project: null,
area: null,
latitude: null,
longitude: null,
images: [],
},
rules: {
'project': {
type: 'string',
required: true,
message: '请选择关联项目',
},
'area': {
type: 'string',
required: true,
message: '请选择地址',
},
'images': {
type: 'array',
required: true,
message: '请上传图片',
},
},
projects: [],
questions: [],
}
},
computed: {
projectDesc() {
const { project } = this.form
const target = this.projects?.find?.(item => item.id === project)
return target?.name || ''
},
},
methods: {
getData() {
// todo
this.projects = [
{
id: '001',
name: '亲子•坝上双草原6日 |乌兰布统+锡林郭勒+长城',
},
{
id: '002',
name: '青青草原•云中岭 |5-10公里AB线强度可选',
},
{
id: '003',
name: '新疆天山行7/9日丨醉美伊犁&吐鲁番双套餐',
},
{
id: '004',
name: '九色甘南|人间净土6日/7日深度游',
},
{
id: '005',
name: '北疆全景12日| 入疆首推!阿勒泰+伊犁+吐鲁番',
},
{
id: '006',
name: '塞上江南•神奇宁夏5日|穿越大漠与历史对话',
},
{
id: '007',
name: '尊享•天山环线9日| 伊犁全景+独库,头等舱大巴',
},
]
this.questions = [
{
id: '001',
label: '这次研学之旅,整体给你留下了怎样的印象?用几个词或几句话简单概括一下',
},
{
id: '002',
label: '在整个行程中,你最喜欢的部分是哪里?为什么?',
},
{
id: '003',
label: '你觉得这次研学的行程安排是否合理?有没有哪些地方让你觉得特别满意或需要改进的?',
},
]
},
async open(id) {
// todo: auto bind project by id?
await this.getData()
const texts = this.questions.map(() => '')
this.form = {
project: null,
tripNum: null,
spotNum: null,
mentorNum: null,
images: [],
texts,
}
this.$refs.popup.open()
},
close() {
this.$refs.popup.close()
},
openRelatePojectPicker() {
this.$refs.reloateProjectPopup.open(this.form.project?.id || null)
},
onRelateProjectChange(id) {
this.form.project = id
},
//
selectAddr() {
// Position.getLocation(res => {
Position.selectAddress(0, 0, success => {
this.setAddress(success)
})
// })
},
//
setAddress(res) {
//
this.form.latitude = res.latitude
this.form.longitude = res.longitude
if (!res.address && res.name) { //
return this.form.area = res.name
}
if (res.address || res.name) {
return this.form.area = res.address + res.name
}
this.form.area = '' //
},
async onPublish() {
try {
await this.$refs.form.validate()
const {
} = this.form
const params = {
}
// todo: fetch
// await this.$fetch('updateAddress', params)
uni.showToast({
icon: 'success',
title: '发布成功',
});
this.$emit('submitted')
this.close()
} catch (err) {
console.log('onSave err', err)
}
},
},
}
</script>
<style lang="scss" scoped>
.popup__view {
width: 100vw;
display: flex;
flex-direction: column;
box-sizing: border-box;
background: #FFFFFF;
border-top-left-radius: 32rpx;
border-top-right-radius: 32rpx;
}
.header {
position: relative;
width: 100%;
padding: 24rpx 0;
box-sizing: border-box;
border-bottom: 2rpx solid #EEEEEE;
.title {
font-family: PingFang SC;
font-weight: 500;
font-size: 34rpx;
line-height: 1.4;
color: #181818;
}
.btn {
font-family: PingFang SC;
font-weight: 500;
font-size: 32rpx;
line-height: 1.4;
color: #8B8B8B;
position: absolute;
top: 26rpx;
left: 40rpx;
}
}
.form {
max-height: 75vh;
padding: 32rpx 40rpx;
box-sizing: border-box;
overflow-y: auto;
&-item {
padding: 8rpx 0 6rpx 0;
& + & {
padding-top: 24rpx;
border-top: 2rpx solid #EEEEEE;
}
&-label {
margin-bottom: 14rpx;
display: flex;
align-items: center;
font-family: PingFang SC;
font-weight: 400;
font-size: 26rpx;
line-height: 1.4;
color: #181818;
.icon {
margin-right: 8rpx;
width: 16rpx;
height: auto;
}
}
&-content {
.text {
padding: 2rpx 0;
font-family: PingFang SC;
font-weight: 400;
font-size: 32rpx;
line-height: 1.4;
&.placeholder {
color: #C6C6C6;
}
}
}
}
}
.row {
justify-content: space-between;
.form-label {
margin: 0;
}
}
.footer {
width: 100%;
padding: 32rpx 40rpx;
box-sizing: border-box;
border-top: 2rpx solid #F1F1F1;
.btn {
width: 100%;
padding: 14rpx 0;
box-sizing: border-box;
font-family: PingFang SC;
font-weight: 500;
font-size: 36rpx;
line-height: 1.4;
color: #FFFFFF;
background-image: linear-gradient(to right, #21FEEC, #019AF9);
border: 2rpx solid #00A9FF;
border-radius: 41rpx;
}
}
</style>

+ 241
- 0
pages_order/live/index.vue View File

@ -0,0 +1,241 @@
<template>
<view class="page__view highlight">
<navbar title="图片直播" leftClick @leftClick="$utils.navigateBack" />
<view class="header">
<image class="icon" :src="detail.image" mode="widthFix"></image>
<view class="flex">
<view class="flex">
<view class="title">{{ detail.title }}</view>
<view class="tag">{{ detail.createTime }}</view>
</view>
<view v-if="isManager" class="flex operate">
<view class="btn btn-add" @click="onAdd">新增记录</view>
</view>
</view>
</view>
<view class="main">
<view class="section" v-for="item in list" :key="item.id">
<view class="section-header">
<view class="avatar">
<!-- todo: check key -->
<image class="avatar-img" src="@/static/image/temp-30.png" mode="scaleToFill"></image>
</view>
<view class="info">
<view class="flex title">
<view>{{ item.name }}</view>
<image class="icon" src="@/static/image/icon-location.png" mode="widthFix"></image>
<view class="address text-ellipsis">{{ item.address }}</view>
</view>
<view class="desc">{{ item.createTime }}</view>
</view>
</view>
<view class="section-content record">
<view class="record-item" v-for="(image, imgIdx) in item.images" :key="imgIdx">
<image class="img" :src="image" mode="scaleToFill"></image>
</view>
</view>
</view>
</view>
<formPopup ref="formPopup" @submitted="getData"></formPopup>
</view>
</template>
<script>
import mixinsList from '@/mixins/list.js'
import SYStackedCarousel from '@/uni_modules/SY-StackedCarousel/components/SY-StackedCarousel/SY-StackedCarousel.vue'
import formPopup from './formPopup.vue'
export default {
mixins: [mixinsList],
components: {
SYStackedCarousel,
formPopup,
},
data() {
return {
id: null,
detail: {},
bannerList: [],
current: 0,
queryParams: {
pageNo: 1,
pageSize: 10,
id: '',
},
// todo
mixinsListApi: '',
// todo: fetch
isManager: true,
}
},
computed: {
swiperCurrent() {
return this.bannerList[this.current]
},
},
onLoad(arg) {
const { id } = arg
this.id = id
this.fetchDetail()
this.queryParams.id = id
this.getData()
},
methods: {
async fetchDetail() {
this.bannerList = [
{
url: '/static/image/temp-23.png',
title: '趣玩新加坡',
createTime: '2025-04-18',
},
{
url: '/static/image/temp-24.png',
title: '坝上双草原',
createTime: '2025-04-18',
},
{
url: '/static/image/temp-25.png',
title: '牛湖线',
createTime: '2025-04-18',
},
]
},
clickHandler(item, index) {
console.log("item: ", item);
console.log("index: ", index);
this.current = index
},
changeHandler(index) {
console.log("当前触发change事件,返回索引: ", index);
},
onAdd() {
this.$refs.formPopup.open()
},
},
}
</script>
<style lang="scss" scoped>
.header {
margin-top: 40rpx;
.title {
margin-top: 12rpx;
font-size: 28rpx;
font-weight: 600;
color: #252545;
}
.tag {
margin-top: 4rpx;
padding: 2rpx 8rpx;
font-size: 24rpx;
color: #00A9FF;
background: #E0F5FF;
border-radius: 8rpx;
}
.operate {
flex: 1;
justify-content: flex-end;
.btn-add {
padding: 6rpx 22rpx;
font-family: PingFang SC;
font-size: 28rpx;
font-weight: 500;
line-height: 1.5;
color: #FFFFFF;
background: linear-gradient(to right, #21FEEC, #019AF9);
border: 2rpx solid #00A9FF;
border-radius: 30rpx;
}
}
}
.main {
padding: 0 40rpx;
}
.section {
margin-top: 40rpx;
&-header {
.avatar {
flex: none;
width: 100rpx;
height: 100rpx;
border: 4rpx solid #FFFFFF;
border-radius: 50%;
overflow: hidden;
&-img {
width: 100%;
height: 100%;
}
}
.info {
flex: 1;
column-gap: 24rpx;
.title {
column-gap: 8rpx;
white-space: nowrap;
font-size: 26rpx;
font-weight: 600;
color: #252545;
.icon {
width: 32rpx;
height: 32rpx;
}
.address {
flex: 1;
font-size: 24rpx;
color: #00A9FF;
}
}
.desc {
margin-top: 8rpx;
}
}
}
&-content {
margin-top: 24rpx;
}
}
.record {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
&-item {
border: 2rpx solid #CDCDCD;
border-radius: 12rpx;
overflow: hidden;
.img {
width: 100%;
height: 100%;
}
}
}
</style>

+ 235
- 0
pages_order/live/list.vue View File

@ -0,0 +1,235 @@
<template>
<view class="page__view highlight">
<navbar title="图片直播" leftClick @leftClick="$utils.navigateBack" />
<view class="swiper">
<SYStackedCarousel
height="536rpx"
:images="bannerList"
:current="current"
:autoplay="true"
horizontalMargin="25"
baseOpacity="0.5"
bgColor="transparent"
padding="0"
@click="clickHandler"
@change="changeHandler"
>
</SYStackedCarousel>
<view class="flex" v-if="swiperCurrent">
<view class="title">{{ swiperCurrent.title }}</view>
<view class="tag">{{ swiperCurrent.createTime }}</view>
</view>
</view>
<view class="main">
<view class="flex header">
<view>图片直播</view>
<view class="btn btn-mark" @click="onMark">标记有我</view>
</view>
<view class="section" v-for="item in list" :key="item.id">
<view class="section-header">
<view class="flex title">
<view>{{ item.title }}</view>
<view v-if="isManager" class="btn btn-add" @click="onAdd(item.id)">新增记录</view>
</view>
<button class="flex btn btn-all" @click="showAll">
<view>查看全部</view>
<image class="icon" src="@/static/image/icon-arrow-right.png" mode="widthFix"></image>
</button>
</view>
<view class="section-content record">
<view class="record-item" v-for="(image, imgIdx) in item.images" :key="imgIdx">
<image class="img" :src="image" mode="scaleToFill"></image>
</view>
</view>
</view>
</view>
<markPopup ref="markPopup"></markPopup>
<formPopup ref="formPopup" @submitted="getData"></formPopup>
</view>
</template>
<script>
import mixinsList from '@/mixins/list.js'
import SYStackedCarousel from '@/uni_modules/SY-StackedCarousel/components/SY-StackedCarousel/SY-StackedCarousel.vue'
import markPopup from '@/pages_order/growing/activity/markPopup.vue'
import formPopup from './formPopup.vue'
export default {
mixins: [mixinsList],
components: {
SYStackedCarousel,
markPopup,
formPopup,
},
data() {
return {
bannerList: [],
current: 0,
queryParams: {
pageNo: 1,
pageSize: 10,
id: '',
},
// todo
mixinsListApi: '',
// todo: fetch
isManager: true,
}
},
computed: {
swiperCurrent() {
return this.bannerList[this.current]
},
},
onLoad() {
this.fetchBanner()
},
methods: {
async fetchBanner() {
this.bannerList = [
{
url: '/static/image/temp-23.png',
title: '趣玩新加坡',
createTime: '2025-04-18',
},
{
url: '/static/image/temp-24.png',
title: '坝上双草原',
createTime: '2025-04-18',
},
{
url: '/static/image/temp-25.png',
title: '牛湖线',
createTime: '2025-04-18',
},
]
},
clickHandler(item, index) {
console.log("item: ", item);
console.log("index: ", index);
this.current = index
},
changeHandler(index) {
console.log("当前触发change事件,返回索引: ", index);
},
onMark() {
this.$refs.markPopup.open(this.id)
},
onAdd() {
this.$refs.formPopup.open()
},
},
}
</script>
<style lang="scss" scoped>
.swiper {
margin-top: 40rpx;
.title {
margin-top: 12rpx;
font-size: 28rpx;
font-weight: 600;
color: #252545;
}
.tag {
margin-top: 4rpx;
padding: 2rpx 8rpx;
font-size: 24rpx;
color: #00A9FF;
background: #E0F5FF;
border-radius: 8rpx;
}
}
.main {
padding: 0 40rpx;
}
.header {
justify-content: space-between;
font-size: 40rpx;
font-weight: 600;
color: #252545;
.btn-mark {
padding: 6rpx 22rpx;
font-family: PingFang SC;
font-size: 28rpx;
font-weight: 500;
line-height: 1.5;
color: #FFFFFF;
background: linear-gradient(to right, #21FEEC, #019AF9);
border: 2rpx solid #00A9FF;
border-radius: 30rpx;
}
}
.section {
margin-top: 64rpx;
&-header {
font-size: 36rpx;
font-weight: 500;
color: #191919;
.title {
flex: 1;
justify-content: flex-start;
column-gap: 8rpx;
.btn-add {
padding: 8rpx 24rpx;
font-size: 28rpx;
font-weight: 500;
line-height: 1.5;
color: #252545;
border: 2rpx solid #252545;
border-radius: 30rpx;
}
}
.btn-all {
column-gap: 4rpx;
font-size: 24rpx;
line-height: 1.4;
color: #8B8B8B;
.icon {
width: 32rpx;
height: auto;
}
}
}
&-content {
margin-top: 24rpx;
}
}
.record {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
&-item {
border: 2rpx solid #CDCDCD;
border-radius: 12rpx;
overflow: hidden;
.img {
width: 100%;
height: 100%;
}
}
}
</style>

+ 13
- 10
pages_order/product/search.vue View File

@ -5,13 +5,13 @@
<navbar title="搜索结果" leftClick @leftClick="$utils.navigateBack" bgColor="transparent" color="#191919" />
<!-- 搜索栏 -->
<view class="flex search">
<view :class="['flex', 'search', isFocusSearch ? 'is-focus' : '']" >
<uv-search
v-model="keyword"
placeholder="输入关键词搜索"
color="#181818"
bgColor="transparent"
:showAction="true"
:showAction="isFocusSearch"
@custom="search"
@search="search"
@focus="isFocusSearch = true"
@ -56,6 +56,7 @@
data() {
return {
keyword: '',
isFocusSearch: false,
queryParams: {
pageNo: 1,
pageSize: 10,
@ -174,14 +175,16 @@
border: 4rpx solid transparent;
}
/deep/ .uv-search__action {
padding: 19rpx 24rpx;
font-size: 26rpx;
font-weight: 500;
line-height: 1;
color: #FFFFFF;
background: #00A9FF;
border-radius: 32rpx;
&.is-focus {
/deep/ .uv-search__action {
padding: 19rpx 24rpx;
font-size: 26rpx;
font-weight: 500;
line-height: 1;
color: #FFFFFF;
background: #00A9FF;
border-radius: 32rpx;
}
}
}


BIN
static/image/icon-location.png View File

Before After
Width: 48  |  Height: 48  |  Size: 1.4 KiB

+ 4
- 0
uni_modules/SY-StackedCarousel/changelog.md View File

@ -0,0 +1,4 @@
## 1.0.0(2024-08-18)
**初始发布**
-
# 1.0.0 (2024-08-16)

+ 421
- 0
uni_modules/SY-StackedCarousel/components/SY-StackedCarousel/SY-StackedCarousel.vue View File

@ -0,0 +1,421 @@
<template>
<view class="sy-swiper" :style="{
backgroundColor: bgColor,
height: addUnit(height),
padding: padding,
borderRadius: addUnit(radius)
}">
<view v-if="showButton" class="sy-swiper__operation left" @click="slideLeft" :style="{
width: addUnit(buttonSize),
height: addUnit(buttonSize),
background: buttonBgColor,
fontSize: addUnit(buttonSize)
}">
<slot name="left-button"></slot>
</view>
<view class="sy-swiper__panel" @touchstart="onTouchStart" @touchend="onTouchEnd">
<view class="sy-swiper__panel-item" v-for="(item, index) in images" :key="index" :style="{
transform: itemStyles[index].transform,
zIndex: itemStyles[index].zIndex,
opacity: itemStyles[index].opacity,
transitionDuration: millisecondsToSeconds(duration) + 's',
transitionTimingFunction: easing
}">
<view class="sy-swiper__panel-item__content" @click="$emit('click', item, index)">
<template v-if="showFirstImageOnly">
<image class="slide" :src="item.url" :mode="imgMode" v-if="itemStyles[index].isTop" :style="{
borderRadius: addUnit(slideRadius)
}"></image>
<view class="slide-mask" v-else :style="{ background: maskBgColor, borderRadius: addUnit(slideRadius) }"></view>
</template>
<template v-else>
<image class="slide" :src="item.url" :mode="imgMode" :style="{ borderRadius: addUnit(slideRadius) }">
</image>
</template>
<text class="desc" :class="{ 'text': descNoWrap }" v-if="showDesc && item.desc" :style="{ background: descBgColor, color: descColor, fontSize: addUnit(descSize), borderRadius: `0 0 ${addUnit(slideRadius)} ${addUnit(slideRadius)}` }">{{ item.desc }}</text>
</view>
</view>
</view>
<view v-if="showButton" class="sy-swiper__operation right" @click="slideRight" :style="{
width: addUnit(buttonSize),
height: addUnit(buttonSize),
background: buttonBgColor,
fontSize: addUnit(buttonSize)
}">
<slot name="right-button"></slot>
</view>
</view>
</template>
<script>
import {
addUnit,
millisecondsToSeconds
} from "./util.js";
export default {
props: {
//
images: {
type: Array,
default: () => []
},
padding: {
type: String,
default: "10px"
},
//
autoplay: {
type: Boolean,
default: false
},
//
easing: {
type: String,
default: "ease-in-out"
},
//
enableOpacity: {
type: Boolean,
default: true
},
//
baseOpacity: {
type: Number,
default: 0.8
},
// index
current: {
type: Number,
default: 0
},
// ms
interval: {
type: Number,
default: 2000
},
// ms
duration: {
type: Number,
default: 500
},
//
horizontalMargin: {
type: [String, Number],
default: 10
},
//
verticalMargin: {
type: [String, Number],
default: 10
},
//
height: {
type: [String, Number],
default: 130
},
//
showButton: {
type: Boolean,
default: false
},
//
buttonSize: {
type: [String, Number],
default: 24
},
//
buttonBgColor: {
type: String,
default: "rgba(0, 0, 0, 0.26)"
},
// imageMode
imgMode: {
type: String,
default: "aspectFill"
},
//
showFirstImageOnly: {
type: Boolean,
default: false
},
//
maskBgColor: {
type: String,
default: "#ffffff"
},
// imagesdesc
showDesc: {
type: Boolean,
default: true,
},
//
descBgColor: {
type: String,
default: "rgba(0, 0, 0, 0.5)"
},
//
descColor: {
type: String,
default: "#ffffff"
},
//
descSize: {
type: [Number, String],
default: 10
},
// ...
descNoWrap: {
type: Boolean,
default: false
},
//
radius: {
type: [String, Number],
default: 4
},
//
bgColor: {
type: String,
default: "#ffffff"
},
slideRadius: {
type: [String, Number],
default: 10
}
},
//
emits: ["click", "change"],
data() {
return {
slideNote: {
x: 0,
y: 0
},
itemStyles: [],
screenWidth: 0,
currentIndex: 0,
autoSlideInterval: null,
autoSlideTimeout: null
}
},
mounted() {
this.autoplay && this.doSomething();
},
onUnload() {
this.autoSlideInterval = null;
this.autoSlideTimeout = null;
},
beforeDestroy() {
clearInterval(this.autoSlideInterval);
clearTimeout(this.autoSlideTimeout);
this.autoSlideInterval = null;
this.autoSlideTimeout = null;
},
watch: {
images: {
handler(newImages) {
var macInfo = uni.getSystemInfoSync();
this.screenWidth = macInfo.screenWidth;
this.itemStyles = newImages.map((_, index) => (this.getStyle(index)));
},
deep: true,
immediate: true
},
current: {
handler(newCurrentIndex) {
this.scrollToCurrent(newCurrentIndex);
},
immediate: true
}
},
methods: {
addUnit,
millisecondsToSeconds,
doSomething() {
this.$nextTick(() => {
this.autoSlideInterval = setInterval(() => {
this.slideRight();
}, this.interval)
})
},
getStyle(eIndex) {
if (eIndex > this.images.length / 2) {
var right = this.images.length - eIndex;
return {
transform: "scale(" + (1 - right / this.verticalMargin) + ") translate(-" + right * this
.horizontalMargin + "%, 0px)",
zIndex: 100 - right,
opacity: this.enableOpacity ? this.baseOpacity / right : 1,
isTop: eIndex === 0
}
} else {
return {
transform: "scale(" + (1 - eIndex / this.verticalMargin) + ") translate(" + eIndex * this
.horizontalMargin + "%, 0px)",
zIndex: 100 - eIndex,
opacity: this.enableOpacity ? this.baseOpacity / eIndex : 1,
isTop: eIndex === 0
}
}
},
restartTimer() {
clearInterval(this.autoSlideInterval);
this.autoSlideInterval = null;
},
scrollToCurrent(currentIndex) {
//
this.restartTimer();
//
for (let i = 0; i < currentIndex; i++) {
this.slideRight();
}
},
simulateStartMove(x) {
this.slideNote.x = x;
},
onTouchStart(e) {
this.restartTimer();
this.slideNote.x = e.changedTouches[0] ? e.changedTouches[0].pageX : 0;
this.slideNote.y = e.changedTouches[0] ? e.changedTouches[0].pageY : 0;
},
onTouchEnd(e) {
var newList = JSON.parse(JSON.stringify(this.itemStyles));
if (e.changedTouches[0].pageX - this.slideNote.x < -10) {
//
var last = [newList.pop()];
newList = last.concat(newList);
this.currentIndex = (this.currentIndex + 1) % this.images.length;
} else if (e.changedTouches[0].pageX - this.slideNote.x >= 10) {
//
newList.push(newList[0]);
newList.splice(0, 1);
this.currentIndex = (this.currentIndex - 1 + this.images.length) % this.images.length;
}
this.itemStyles = newList;
if (this.autoplay) {
clearInterval(this.autoSlideInterval);
clearTimeout(this.autoSlideTimeout);
// 使 this.interval
this.autoSlideTimeout = setTimeout(() => {
this.slideRight();
}, this.interval);
}
},
slideLeft() {
this.restartTimer();
this.simulateStartMove(this.slideNote.x); // touchstart slideNote.x
this.onTouchEnd({
changedTouches: [{
pageX: this.slideNote.x + 20
}]
});
// change
this.$emit('change', this.currentIndex);
},
slideRight() {
this.restartTimer();
this.simulateStartMove(this.slideNote.x); // touchstart slideNote.x
this.onTouchEnd({
changedTouches: [{
pageX: this.slideNote.x - 20
}]
});
// change
this.$emit('change', this.currentIndex);
}
}
}
</script>
<style lang="scss" scoped>
.sy-swiper {
position: relative;
box-sizing: border-box;
.sy-swiper__operation {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
border-radius: 50%;
background: rgba(0, 0, 0, 0.26);
z-index: 999;
&:active {
filter: brightness(70%);
}
}
.sy-swiper__operation.left {
top: 50%;
left: 10px;
transform: translateY(-50%);
}
.sy-swiper__operation.right {
bottom: 50%;
right: 10px;
transform: translateY(50%);
}
.sy-swiper__panel {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
.sy-swiper__panel-item {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
transition-property: transform, opacity;
// transition-duration: 0.4s;
// transition-timing-function: ease-in-out;
&__content {
height: 100%;
width: 330rpx;
margin: 2rpx auto;
position: relative;
.slide {
height: 100%;
width: 100%;
}
.slide-mask {
position: relative;
height: 100%;
width: 100%;
z-index: 1;
}
.desc {
position: absolute;
width: 100%;
bottom: 0;
left: 0;
padding: 10rpx 20rpx;
box-sizing: border-box;
box-shadow: 0rpx 4rpx 21rpx 0rpx rgba(0, 0, 0, 0.07);
// border-radius: 0 0 20px 20px;
font-family: PingFang SC;
font-weight: 400;
line-height: 32rpx;
text-transform: none;
}
}
}
}
}
.text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>

+ 22
- 0
uni_modules/SY-StackedCarousel/components/SY-StackedCarousel/util.js View File

@ -0,0 +1,22 @@
/**
* 验证十进制数字
*/
export function isNumber(value) {
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
}
export function addUnit(value = 'auto', unit = '') {
if (!unit) {
unit = 'px'
}
if (unit == 'rpx') {
value = value * 2
}
value = String(value)
// 用内置验证规则中的number判断是否为数值
return isNumber(value) ? `${value}${unit}` : value
}
export function millisecondsToSeconds(milliseconds) {
return Math.round(milliseconds / 1000 * 100) / 100; // 将结果四舍五入到两位小数
}

+ 85
- 0
uni_modules/SY-StackedCarousel/package.json View File

@ -0,0 +1,85 @@
{
"id": "SY-StackedCarousel",
"displayName": "堆叠轮播图、3D轮播图",
"version": "1.0.0",
"description": "堆叠(层叠)式轮播图组件,3D轮播图,微信小程序通用。让您不再为特殊轮播需求而烦恼。",
"keywords": [
"堆叠轮播图",
"层叠轮播图",
"3D轮播图",
"轮播图",
"轮播"
],
"repository": "",
"engines": {
"HBuilderX": "^3.7.12"
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

+ 117
- 0
uni_modules/SY-StackedCarousel/readme.md View File

@ -0,0 +1,117 @@
## SY-StackedCarousel
该组件一般用于导航轮播,广告展示等场景,可开箱即用,具有如下特点:
- 3D 轮播图效果,满足不同的开发需求
- 可配置显示标题,涵盖不同的应用场景
- 功能属性齐全丰富
- 轻量级,支持小程序、H5 等平台(支持 vue2、vue3)
### 使用方式
```html
<template>
<view class="container">
<SYStackedCarousel
height="480rpx"
autoplay
:images="images"
:current="3"
:interval="2000"
@click="clickHandler"
@change="changeHandler"
>
</SYStackedCarousel>
</view>
</template>
<script>
import SYStackedCarousel from "@/uni_modules/SY-StackedCarousel/components/SY-StackedCarousel.vue";
export default {
components: {
SYStackedCarousel,
},
data() {
return {
images: [
{
url: "../../static/images/1.jpg",
desc: "星辰大海任我行,风雨兼程志更坚。星辰大海任我行,风雨兼程志更坚。星辰大海任我行,风雨兼程志更坚。星辰大海任我行,风雨兼程志更坚。",
},
{
url: "../../static/images/2.jpg",
desc: "晨曦微露破晓时,梦想起航正当时。",
},
{
url: "../../static/images/3.jpg",
desc: "千锤百炼钢更强,逆境之中显真章。",
},
{
url: "../../static/images/4.jpg",
desc: "心有猛虎细嗅蔷薇,勇者无畏亦柔情。",
},
{
url: "../../static/images/5.jpg",
desc: "高山仰止景行行,志士仁人共长天。",
},
],
};
},
onLoad() {},
methods: {
clickHandler(item, index) {
console.log("item: ", item);
console.log("index: ", index);
},
changeHandler(index) {
console.log("当前触发change事件,返回索引: ", index);
},
},
};
</script>
```
### 属性说明
| 属性名 | 类型 | 默认值 | 说明 |
| :-----------------| :---------------| :------------------ | :--------------------------------------------------------- |
| images | Array | [] | 图片数组 |
| height | [String, Number]| 300rpx | 轮播图高度 |
| autoplay | Boolean | true | 是否自动播放 |
| padding | String | 10px | 轮播内边距 |
| radius | [String, Number]| 4 | 组件圆角值 |
| bgColor | String | #ffffff | 容器背景色 |
| interval | Number | 2000 | 滑块自动切换时间间隔(ms) |
| current | Number | 0 | 当前显示的图片索引 |
| easing | String | ease-in-out | 动画类型 |
| enableOpacity | Boolean | true | 是否启用不透明度设定(堆叠每一层逐渐变透明) |
| baseOpacity | Number | 0.8 | 最顶层图片的不透明度,每一层会根据这个值自动缩小 opacity值 |
| duration | Number | 500 | 滑块切换过程所需时间(ms) |
| horizontalMargin | [String, Number]| 10 | 横向间距 |
| verticalMargin | [String, Number]| 10 | 纵向间距 |
| showButton | Boolean | false | 是否显示操作按钮 |
| buttonSize | [String, Number]| 24 | 按钮大小以及按钮图标大小 |
| buttonBgColor | String | rgba(0, 0, 0, 0.26) | 按钮背景颜色 |
| imgMode | String | aspectFill | 图片裁剪模式,详情见微信原生 imageMode |
| showFirstImageOnly| Boolean | false | 是否只显示第一张图片,其他图片被蒙版遮住 |
| maskBgColor | String | #ffffff | 遮住蒙版的颜色 |
| showDesc | Boolean | true | 是否显示图片描述(需要 images 传递的数据中存在 desc 属性) |
| descBgColor | String | rgba(0, 0, 0, 0.5) | 底部描述的背景颜色 |
| descColor | String | #ffffff | 底部描述字体颜色 |
| descSize | [String, Number]| 10 | 底部描述字体大小 |
| descNoWrap | Boolean | false | 描述是否不换行,超出后用...省略 |
| slideRadius | [String, Number]| 10 | 图片的圆角值 |
### 事件说明
| 事件名 | 说明 | 回调参数 |
| :----- | :----------------------------------- | :--------------------------------------------------------- |
| click | 点击图片触发 | item:当前图片对象信息, index:当前是第几张图片,从 0 开始 |
| change | 轮播图切换时触发(自动或者手动切换) | index:当前是第几张图片,从 0 开始 |
### 插槽
| 插槽名称 | 说明 |
| :----- | :-----------------------------------|
| left-button | 翻页按钮左侧图标 |
| right-button| 翻页按钮右侧图标 |

Loading…
Cancel
Save