Browse Source

feat: page-growing;

pull/2/head
Fox-33 6 months ago
parent
commit
7f83e5198c
45 changed files with 2600 additions and 276 deletions
  1. +3
    -207
      api/model/index.js
  2. +12
    -4
      common.scss
  3. +7
    -0
      components/base/tabbar.vue
  4. +113
    -0
      components/growing/recordsView.vue
  5. +212
    -0
      components/growing/userCard.vue
  6. +2
    -2
      config.js
  7. +21
    -0
      pages.json
  8. +193
    -0
      pages/index/growing.vue
  9. +5
    -18
      pages/index/index.vue
  10. +29
    -38
      pages_order/comment/commentCard.vue
  11. +28
    -0
      pages_order/components/formRate.vue
  12. +49
    -0
      pages_order/components/formTextarea.vue
  13. +105
    -0
      pages_order/components/formUpload.vue
  14. +6
    -6
      pages_order/coupon/index.vue
  15. +175
    -0
      pages_order/growing/achievement/index.vue
  16. +112
    -0
      pages_order/growing/achievement/recordsView.vue
  17. +277
    -0
      pages_order/growing/activity/index.vue
  18. +261
    -0
      pages_order/growing/activity/markPopup.vue
  19. +353
    -0
      pages_order/growing/activity/recordFormPopup.vue
  20. +121
    -0
      pages_order/growing/activity/reloateProjectPopup.vue
  21. +171
    -0
      pages_order/growing/activity/search.vue
  22. +107
    -0
      pages_order/growing/activity/sortBar.vue
  23. +95
    -0
      pages_order/member/memberCard.vue
  24. +139
    -0
      pages_order/member/switch.vue
  25. +0
    -1
      pages_order/order/orderConfirm/infoPopup.vue
  26. BIN
      static/image/icon-comment.png
  27. BIN
      static/image/icon-like-active.png
  28. BIN
      static/image/icon-like.png
  29. BIN
      static/image/icon-mark-highlight.png
  30. BIN
      static/image/icon-mark.png
  31. BIN
      static/image/icon-question.png
  32. BIN
      static/image/temp-37.png
  33. BIN
      static/image/temp-38.png
  34. BIN
      static/image/temp-39.png
  35. BIN
      static/image/temp-40.png
  36. BIN
      static/image/temp-41.png
  37. BIN
      static/image/temp-42.png
  38. BIN
      static/image/temp-43.png
  39. BIN
      static/image/temp-44.png
  40. BIN
      static/image/temp-45.png
  41. BIN
      static/image/temp-46.png
  42. BIN
      static/image/temp-47.png
  43. BIN
      static/image/temp-48.png
  44. BIN
      static/image/temp-49.png
  45. +4
    -0
      store/store.js

+ 3
- 207
api/model/index.js View File

@ -1,215 +1,11 @@
// 首页相关接口
const api = {
// 获取首页轮播图
getRiceBanner: {
url: '/index_common/getRiceBanner',
// 首页-查询banner图列表
queryBannerList: {
url: '/index/queryBannerList',
method: 'GET',
},
// 获取首页常规产品【废弃】
// getRiceCommonProductList: {
// url: '/index_common/getRiceCommonProductList',
// method: 'GET',
// },
// 获取首页跳转图标
getRiceIconList: {
url: '/index_common/getRiceIconList',
method: 'GET',
},
// 获取首页新闻详情
getRiceNewsDetail: {
url: '/index_common/getCommonNewsDetail',
method: 'GET',
},
// 获取首页新闻列表
getRiceNewsList: {
url: '/index_common/getRiceNewsList',
method: 'GET',
},
// 获取首页公告列表
getRiceNoticeList: {
url: '/index_common/getRiceNoticeList',
method: 'GET',
},
// 获取首页商品详情
getRiceProductDetail: {
url: '/index_common/getRiceProductDetail',
method: 'GET',
},
// 获取首页体验产品
getRiceProductList: {
url: '/index_common/getRiceProductList',
method: 'GET',
},
// 查询分类接口
getCategoryList: {
url: '/index_common/getCategoryList',
method: 'GET',
},
// 新查询分类以及商品数据接口
getCategoryPidList: {
url: '/index_common/getCategoryPidList',
method: 'GET',
debounce : 250,
},
// 查询一级分类接口
getPidList: {
url: '/index_common/getCategoryPidList',
method: 'GET',
},
// 获取分类分页商品列表接口
getClassShopPageList: {
url: '/index_common/getClassShopPageList',
method: 'GET',
},
// 加入购物车
addCart: {
url: '/index_common/addCart',
method: 'GET',
auth: true,
showLoading: true,
limit : 500,
},
// 删除购物车信息
deleteCart: {
url: '/index_common/deleteCart',
method: 'DELETE',
auth: true,
showLoading: true,
},
// 修改购物车信息数量
updateCartNum: {
url: '/index_common/updateCartNum',
method: 'POST',
auth: true,
debounce: 300,
},
// 创建订单
createOrder: {
url: '/index_common/createOrder',
method: 'GET',
auth: true,
limit: 1000,
showLoading: true,
},
// 创建订单-再次支付
createOrderTwo: {
url: '/index_common/createOrderTwo',
method: 'GET',
auth: true,
limit: 1000,
showLoading: true,
},
// 多商品创建订单
createSumOrder: {
url: '/index_common/createSumOrder',
method: 'POST',
auth: true,
limit: 1000,
showLoading: true,
},
// 多商品订单再次支付
createSumOrderAgain: {
url: '/index_common/createSumOrderAgain',
method: 'POST',
auth: true,
limit: 1000,
showLoading: true,
},
// 确认收货
confirmOrder: {
url: '/index_common/confirmOrder',
method: 'GET',
auth: true,
limit: 1000,
showLoading: true,
},
// 取消订单
cancelOrder: {
url: '/index_common/cancelOrder',
method: 'GET',
auth: true,
limit: 1000,
showLoading: true,
},
// 获取首页广告列表
getRiceProductList: {
url: '/index_common/getRiceAdList',
method: 'GET',
},
// 获取首页广告列表
getRiceAdDetail: {
url: '/index_common/getRiceAdDetail',
method: 'GET',
},
//获取优惠券信息
getRiceCouponList: {
url: '/info_common/getRiceCouponList',
method: 'GET',
},
//增加或者修改合伙人申请信息
addOrUpdateCommonUser: {
url: '/index_common/addOrUpdateCommonUser',
method: 'POST',
},
//根据用户查询渠合伙人申请信息表单
getCommonUser: {
url: '/index_common/getCommonUser',
method: 'GET'
},
//提交反馈信息
addFeedback: {
url: '/info_common/addFeedback',
method: 'POST'
},
// 获取我的直接推荐间接推荐用户列表带分页
getHanHaiMemberUser: {
url: '/info_common/getHanHaiMemberUser',
method: 'GET'
},
// 获取祝福背景图
getRiceBlessing: {
url: '/index_common/getRiceBlessing',
method: 'GET'
},
// 随机获取祝福语
getRiceBlessingWords: {
url: '/index_common/getRiceBlessingWords',
method: 'GET'
},
// 根据订单标识修改订单祝福语背景
updateOrderBlessing: {
url: '/index_common/updateOrderBlessing',
method: 'POST',
auth : true,
limit : 1000,
},
// 1.收礼流程 =》点击收礼
getGiveShop: {
url: '/index_common/getGiveShop',
method: 'GET',
auth : true,
limit : 1000,
},
// 2.点击抽奖 =》抽奖
getGiveShopLottery: {
url: '/index_common/getGiveShopLottery',
method: 'GET',
auth : true,
limit : 1000,
},
// 获取我的礼品订单
getMyGiftOrder: {
url: '/index_common/getMyGiftOrder',
method: 'GET',
auth : true,
},
// 获取我的礼品订单详情
getMyGiftOrderDetail: {
url: '/index_common/getMyGiftOrderDetail',
method: 'GET',
auth : true,
},
}
export default api

+ 12
- 4
common.scss View File

@ -60,6 +60,9 @@
font-weight: 400;
line-height: 1.4;
}
.page__view.highlight {
background: linear-gradient(#DAF3FF, #FBFEFF 400rpx, #FBFEFF);
}
.btn {
width: auto;
@ -78,10 +81,6 @@
padding: 0 !important;
}
/deep/ .uv-calendar-month__days__day {
// height: 60px !important;
}
/deep/ .uv-calendar-month__days__day__select__top-info,
/deep/ .uv-calendar-month__days__day__select__buttom-info {
white-space: nowrap;
@ -93,4 +92,13 @@
/deep/ .uv-number-box__input {
font-size: 40rpx;
}
/deep/ .uv-steps-item__wrapper {
background: transparent !important;
}
/deep/ .uv-steps-item__line {
background: #F3F3F3 !important;
transform: translateY(9px) !important;
}

+ 7
- 0
components/base/tabbar.vue View File

@ -43,6 +43,13 @@
"title": "分类",
key: 'category',
},
{
"selectedIconPath": "/static/image/tabbar/growing-active.png",
"iconPath": "/static/image/tabbar/growing.png",
"pagePath": "/pages/index/growing",
"title": "成长档案",
key: 'growing',
},
{
"selectedIconPath": "/static/image/tabbar/user-center-active.png",
"iconPath": "/static/image/tabbar/user-center.png",


+ 113
- 0
components/growing/recordsView.vue View File

@ -0,0 +1,113 @@
<template>
<view class="records__view">
<uv-steps direction="column">
<uv-steps-item v-for="(record, index) in list" :key="record.id">
<template #icon>
<view class="flex mark is-active" v-if="index === 0">
<image class="icon" src="@/static/image/icon-mark-highlight.png" mode="widthFix"></image>
</view>
<view class="flex mark" v-else>
<image class="icon" src="@/static/image/icon-mark.png" mode="widthFix"></image>
</view>
</template>
<template #title>
<view class="title" @click="onClickActivity(record.id)">{{ record.createTime }}</view>
</template>
<template #desc>
<view class="content" @click="onClickActivity(record.id)">
<view class="desc">{{ record.name }}</view>
<view class="image">
<view class="image-item" v-for="(image, imgIdx) in record.image" :key="imgIdx">
<image class="img" :src="image" mode="scaleToFill"></image>
</view>
</view>
</view>
</template>
</uv-steps-item>
</uv-steps>
</view>
</template>
<script>
export default {
props: {
list: {
type: Array,
default() {
return []
}
}
},
methods: {
onClickActivity(id) {
this.$utils.navigateTo(`/pages_order/growing/activity/index?id=${id}`)
},
},
}
</script>
<style scoped lang="scss">
.records__view {
padding-left: 40rpx;
}
.mark {
margin-top: 8rpx;
width: 36rpx;
height: 36rpx;
background: #F3F3F3;
border-radius: 50%;
.icon {
width: 24rpx;
height: auto;
}
&.is-active {
background: linear-gradient(to right, #21FEEC, #019AF9);
}
}
.title {
font-family: PingFang SC;
font-size: 36rpx;
font-weight: 600;
line-height: 1.4;
color: $uni-color;
}
.content {
width: calc(100vw - 92rpx);
}
.desc {
font-family: PingFang SC;
font-size: 28rpx;
font-weight: 400;
line-height: 1.4;
color: #191919;
}
.image {
display: flex;
column-gap: 16rpx;
flex-wrap: nowrap;
overflow-x: auto;
&-item {
flex: none;
width: 208rpx;
height: 296rpx;
border: 2rpx solid #CDCDCD;
border-radius: 12rpx;
overflow: hidden;
.img {
width: 100%;
height: 100%;
}
}
}
</style>

+ 212
- 0
components/growing/userCard.vue View File

@ -0,0 +1,212 @@
<template>
<view class="card">
<view class="flex card-header">
<view>
<view class="title">我的档案</view>
<button class="flex btn btn-question">
<image class="btn-icon" src="@/static/image/icon-question.png" mode="widthFix"></image>
<view>如何完善我的档案</view>
</button>
</view>
<view class="flex">
<button class="btn btn-switch" @click="onSwitch">切换</button>
<button class="btn btn-add" @click="onAdd">新增记录</button>
</view>
</view>
<view class="card-content">
<view class="flex info">
<view class="avatar">
<image class="img" src="@/static/image/temp-30.png" mode="scaleToFill"></image>
<view class="tag">学生</view>
</view>
<view class="flex summary">
<view class="flex flex-column summary-item name">
<view class="summary-item-content">战斗世界</view>
<view class="summary-item-label">ID5625354</view>
</view>
<view class="flex flex-column summary-item" @click="jumpToAchievement">
<view class="summary-item-content">8</view>
<view class="summary-item-label">成就</view>
</view>
<view class="flex flex-column summary-item">
<view class="summary-item-content">68</view>
<view class="summary-item-label">足迹</view>
</view>
</view>
</view>
<view class="flex medal">
<image class="medal-item" src="@/static/image/temp-47.png" mode="widthFix"></image>
<image class="medal-item" src="@/static/image/temp-48.png" mode="widthFix"></image>
<image class="medal-item" src="@/static/image/temp-49.png" mode="widthFix"></image>
<image class="medal-item" src="@/static/image/temp-47.png" mode="widthFix"></image>
<image class="medal-item" src="@/static/image/temp-48.png" mode="widthFix"></image>
<image class="medal-item" src="@/static/image/temp-49.png" mode="widthFix"></image>
<image class="medal-item" src="@/static/image/temp-47.png" mode="widthFix"></image>
<image class="medal-item" src="@/static/image/temp-48.png" mode="widthFix"></image>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
onLoad() {
},
methods: {
getData() {
// todo
},
onAdd() {
this.$emit('addRecord')
},
onSwitch() {
this.$emit('switchMember')
},
jumpToAchievement() {
uni.navigateTo({
url: '/pages_order/growing/achievement/index'
})
},
},
}
</script>
<style scoped lang="scss">
.card {
font-family: PingFang SC;
font-weight: 400;
line-height: 1.4;
background: linear-gradient(to right, #DDF4FF, #9FE1FF);
border-radius: 48rpx;
&-header {
justify-content: space-between;
padding: 24rpx 40rpx 20rpx 40rpx;
.title {
font-size: 32rpx;
font-weight: 600;
color: #000000;
}
.btn-question {
margin-top: 4rpx;
column-gap: 8rpx;
font-size: 26rpx;
color: #21607D;
.btn-icon {
width: 32rpx;
height: auto;
}
}
.btn-switch {
padding: 6rpx 22rpx;
font-size: 28rpx;
font-weight: 400;
line-height: 1.4;
color: #252545;
border: 2rpx solid #252545;
border-radius: 28rpx;
}
.btn-add {
padding: 8rpx 24rpx;
font-size: 28rpx;
font-weight: 500;
line-height: 1.4;
color: #FFFFFF;
background: linear-gradient(to right, #21FEEC, #019AF9);
border-radius: 28rpx;
}
.btn + .btn {
margin-left: 26rpx;
}
}
&-content {
padding: 38rpx 38rpx 14rpx 38rpx;
background: linear-gradient(to right, #FBFEFF, #DAF3FF);
border: 2rpx solid #FFFFFF;
border-radius: 48rpx;
box-shadow: 0 2px 12px 0 #009AE717;
.info {
column-gap: 24rpx;
.avatar {
flex: none;
position: relative;
width: 128rpx;
height: 128rpx;
border-radius: 24rpx;
overflow: hidden;
.img {
width: 100%;
height: 100%;
}
.tag {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
padding: 2rpx 0;
box-sizing: border-box;
text-align: center;
font-size: 24rpx;
color: #03C25C;
background: #E9FFF5;
}
}
.summary {
flex: 1;
column-gap: 26rpx;
&-item {
flex: 1;
row-gap: 8rpx;
&.name {
flex: none;
}
&-content {
font-size: 32rpx;
font-weight: 600;
color: #000000;
}
&-label {
font-size: 24rpx;
color: #939393;
}
}
}
}
.medal {
margin-top: 16rpx;
justify-content: flex-start;
flex-wrap: wrap;
gap: 16rpx;
&-item {
width: 50rpx;
height: auto;
}
}
}
}
</style>

+ 2
- 2
config.js View File

@ -8,7 +8,7 @@ import uvUI from '@/uni_modules/uv-ui-tools'
Vue.use(uvUI);
// 当前环境
const type = 'dev'
const type = 'prod'
// 环境配置
@ -17,7 +17,7 @@ const config = {
baseUrl : 'http://www.gcosc.fun:82',
},
prod : {
baseUrl : 'http://xxx.xxx.xxx/xxx',
baseUrl : 'https://www.hongyujiaoyu.com/studytour-admin/studytour',
}
}


+ 21
- 0
pages.json View File

@ -12,6 +12,15 @@
"navigationBarTitleText": ""
}
},
{
"path": "pages/index/growing",
"style": {
"navigationBarTitleText": "",
"componentPlaceholder": {
"record-form-popup": "view"
}
}
},
{
"path": "pages/index/center",
"style": {
@ -69,6 +78,18 @@
},
{
"path": "coupon/index"
},
{
"path": "growing/activity/search"
},
{
"path": "growing/activity/index"
},
{
"path": "growing/achievement/index"
},
{
"path": "member/switch"
}
]
}],


+ 193
- 0
pages/index/growing.vue View File

@ -0,0 +1,193 @@
<template>
<view class="page__view highlight">
<!-- 搜索栏 -->
<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 class="archives">
<userCard @switchMember="jumpToChooseMember" @addRecord="onAdd"></userCard>
</view>
<view class="list" v-if="memberInfo">
<recordsView :list="list"></recordsView>
</view>
<view class="flex" v-else>
<button class="btn btn-choose" @click="jumpToChooseMember">请先选择人员</button>
</view>
<record-form-popup ref="recordFormPopup" @submitted="getData"></record-form-popup>
<tabber select="growing" />
</view>
</template>
<script>
import { mapState } from 'vuex'
import mixinsList from '@/mixins/list.js'
import tabber from '@/components/base/tabbar.vue'
import userCard from '@/components/growing/userCard.vue'
import recordsView from '@/components/growing/recordsView.vue'
import recordFormPopup from '@/pages_order/growing/activity/recordFormPopup.vue'
export default {
mixins: [mixinsList],
components: {
userCard,
recordsView,
recordFormPopup,
tabber,
},
data() {
return {
keyword: '',
isFocusSearch: false,
// todo
mixinsListApi: '',
}
},
computed: {
...mapState(['memberInfo']),
},
methods: {
// todo: delete
getData() {
this.list = [
{
id: '001',
name: '新疆天山行7/9日丨醉美伊犁&吐鲁番双套餐',
image: [
'/static/image/temp-38.png',
'/static/image/temp-39.png',
'/static/image/temp-40.png',
],
createTime: '2025-07-12',
},
{
id: '002',
name: '仙踪新昌·韩妃江古道|邂逅“江南小桂林”',
image: [
'/static/image/temp-41.png',
'/static/image/temp-42.png',
'/static/image/temp-43.png',
],
createTime: '2025-06-18',
},
{
id: '003',
name: '山水石窟·大佛寺|江南佛窟造像,新昌山水轻徒',
image: [
'/static/image/temp-44.png',
'/static/image/temp-45.png',
'/static/image/temp-46.png',
],
createTime: '2025-06-15',
},
]
},
search() {
console.log('search', this.keyword)
uni.navigateTo({
url: '/pages_order/growing/activity/search?search=' + this.keyword
})
// this.keyword = ''
},
onAdd() {
this.$refs.recordFormPopup.open()
},
jumpToChooseMember() {
uni.navigateTo({
url: `/pages_order/member/switch`
})
},
},
}
</script>
<style scoped lang="scss">
.page__view {
/deep/ .tabbar-box {
.tabbar {
z-index: 10000;
}
}
}
.search {
$w: 474rpx;
$h: 64rpx;
$radius: 32rpx;
$borderWidth: 4rpx;
width: $w;
height: $h;
position: relative;
padding: 94rpx 32rpx 6rpx 32rpx;
border-radius: $radius;
&-icon {
margin: 0 13rpx 0 26rpx;
width: 30rpx;
height: auto;
}
/deep/ .uv-search__content {
padding: 12rpx 0;
border: 4rpx solid transparent;
}
/deep/ .uv-search__content {
background: #FFFFFF !important;
border-color: #CFEFFF !important;
}
&.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;
}
}
}
.archives {
padding: 32rpx 40rpx 16rpx 40rpx;
}
.btn-choose {
margin-top: 84rpx;
padding: 14rpx 125rpx;
font-size: 36rpx;
font-weight: 500;
line-height: 1.4;
color: #FFFFFF;
background: linear-gradient(to right, #21FEEC, #019AF9);
border: 2rpx solid #00A9FF;
border-radius: 41rpx;
}
</style>

+ 5
- 18
pages/index/index.vue View File

@ -50,7 +50,7 @@
</view>
<view class="section">
<productView :list="list" @categoryChange="onCategoryChange"></productView>
<productView :list="list"></productView>
</view>
</view>
@ -93,13 +93,14 @@
}
},
onLoad() {
// this.$utils.navigateTo(`/pages_order/growing/activity/index`)
// uni.navigateTo({
// url: `/pages_order/order/orderConfirm/index`
// })
// return
uni.navigateTo({
url: `/pages_order/order/orderDetail/index`
})
// uni.navigateTo({
// url: `/pages_order/order/orderDetail/index`
// })
// let id = '1948353988875821058'
// uni.navigateTo({
@ -176,20 +177,6 @@
]
this.total = this.list.length
},
onCategoryChange(e) {
const { classId } = e || {}
if (classId) {
this.queryParams.classId = classId
} else {
delete this.queryParams.classId
}
this.queryParams.pageNo = 1
this.queryParams.pageSize = 10
this.getData()
},
},
}
</script>


+ 29
- 38
pages_order/comment/commentCard.vue View File

@ -10,15 +10,8 @@
<view class="info">
<view class="name">{{ data.user.name }}</view>
<view>{{ $dayjs(data.createTime).format('YYYY-MM-DD') }}</view>
<!-- todo: check key -->
<!-- <view>{{ `${data.countDesc || ''} | ${$dayjs(data.createTime).format('YYYY-MM-DD')}` }}</view> -->
</view>
</view>
<view class="right" v-if="mode == 'edit'">
<button class="btn" @click="onDelete">
<image class="btn-icon" src="@/pages_order/static/comment/icon-delete.png" mode="widthFix"></image>
</button>
</view>
</view>
<view class="section content">{{ data.content }}</view>
<view class="flex section imgs">
@ -42,6 +35,20 @@
<uv-rate :value="data.logisticsNum" size="48rpx" gutter="16rpx" activeColor="#F7BA1E" :allowHalf="true" :minCount="0.5" readonly></uv-rate>
</view>
</view>
<view class="section operate" v-if="mode == 'edit'">
<button class="flex btn" @click="onComment">
<image class="icon" src="@/static/image/icon-comment.png" mode="aspectFill"></image>
<view>评论</view>
</button>
<button v-if="data.liked" class="flex btn" @click="onLike">
<image class="icon" src="@/static/image/icon-like-active.png" mode="aspectFill"></image>
<view>点赞</view>
</button>
<button v-else class="flex btn" @click="onCancelLike">
<image class="icon" src="@/static/image/icon-like.png" mode="aspectFill"></image>
<view>点赞</view>
</button>
</view>
</view>
</template>
@ -67,37 +74,21 @@
}
},
methods: {
async fetchDelete() {
uni.showToast({
icon: 'loading',
title: '正在删除',
});
try {
await this.$fetch('deleteEvaluate', { id: this.data.id })
uni.showToast({
icon: 'success',
title: '删除成功',
});
this.$emit('deleteSucc')
} catch (err) {
}
},
onDelete() {
uni.showModal({
title: '确认删除?',
success : e => {
if(e.confirm){
this.fetchDelete()
}
}
})
}
onComment() {
// todo
this.$emit('change')
},
onLike() {
// todo
this.$emit('change')
},
onCancelLike() {
// todo
this.$emit('change')
},
},
}
</script>


+ 28
- 0
pages_order/components/formRate.vue View File

@ -0,0 +1,28 @@
<template>
<uv-rate
:value="value"
@input="$emit('input', $event)"
@change="$emit('input', $event)"
activeIcon="star-fill"
inactiveIcon="star-fill"
size="48rpx"
gutter="16rpx"
activeColor="#F7BA1E"
:allowHalf="true"
:minCount="0.5"
></uv-rate>
</template>
<script>
export default {
props: {
value: {
default: null
},
},
}
</script>
<style>
</style>

+ 49
- 0
pages_order/components/formTextarea.vue View File

@ -0,0 +1,49 @@
<template>
<uv-textarea
:value="value"
@input="$emit('input', $event)"
:placeholder="placeholder"
:height="height"
border="none"
:customStyle="{
backgroundColor: '#F7F8FA',
padding: '8rpx 16rpx',
borderRadius: '16rpx',
}"
:placeholderStyle="{
color: '#C6C6C6',
fontSize: '32rpx',
}"
:textStyle="{
fontSize: '32rpx',
}"
></uv-textarea>
</template>
<script>
export default {
props: {
value: {
default: null
},
placeholder: {
type: String,
default: '请输入'
},
height: {
type: String,
default: '230rpx'
},
},
data() {
return {
}
},
methods: {
},
}
</script>
<style scoped lang="scss">
</style>

+ 105
- 0
pages_order/components/formUpload.vue View File

@ -0,0 +1,105 @@
<template>
<view>
<uv-upload
:fileList="displayFileList"
:accept="accept"
:maxCount="maxCount"
:width="width"
:height="height"
@afterRead="afterRead"
@delete="deleteFile"
>
<button class="flex btn">
<image class="btn-icon" src="@/static/image/icon-plus.png" mode="widthFix" />
</button>
</uv-upload>
</view>
</template>
<script>
export default {
props: {
value: {
type: Array,
default() {
return []
}
},
accept: {
type: String,
default: 'image' // all | media | image | file | video
},
width: {
type: Number | String,
default: 190,
},
height: {
type: Number | String,
default: 190,
},
maxCount: {
type: Number | String,
default: 100,
}
},
data() {
return {
}
},
computed: {
fileList: {
set(val) {
this.$emit('input', val)
},
get() {
return this.value
}
},
displayFileList() {
return this.fileList.map(url => ({ url }))
},
},
watch: {
fileList: {
handler(val) {
console.log('watch fileList', val)
},
deep: true,
},
displayFileList: {
handler(val) {
console.log('watch displayFileList', val)
},
deep: true,
},
},
methods: {
deleteFile(e){
console.log('deleteFile', e)
const fileList = [...this.fileList]
fileList.splice(e.index, 1)
this.fileList = fileList
},
afterRead(e){
this.$Oss.ossUpload(e.file.url).then(url => {
this.fileList = this.fileList.concat(url)
})
},
},
}
</script>
<style scoped lang="scss">
.btn {
width: 190rpx;
height: 190rpx;
background: #F3F2F7;
border-radius: 16rpx;
&-icon {
width: 61rpx;
height: auto;
}
}
</style>

+ 6
- 6
pages_order/coupon/index.vue View File

@ -13,12 +13,12 @@
activeColor="#00A9FF"
@change="onRadioChange"
>
<view class="list-item" v-for="item in list" :key="item.id">
<couponCard
:data="item"
@select="onSelect"
></couponCard>
</view>
<view class="list-item" v-for="item in list" :key="item.id">
<couponCard
:data="item"
@select="onSelect"
></couponCard>
</view>
</uv-radio-group>
</view>


+ 175
- 0
pages_order/growing/achievement/index.vue View File

@ -0,0 +1,175 @@
<template>
<view class="page__view highlight">
<!-- 导航栏 -->
<navbar title="我的成就" leftClick @leftClick="$utils.navigateBack" />
<view class="main">
<view class="flex summary">
<view class="info">
<view class="flex title">共获得<view class="highlight">8</view>枚成就</view>
<view class="tag">新获得</view>
</view>
<view class="icon">
<image class="img" src="@/static/image/temp-49.png" mode="widthFix"></image>
</view>
</view>
<view class="list">
<recordsView :list="list"></recordsView>
</view>
</view>
</view>
</template>
<script>
import mixinsList from '@/mixins/list.js'
import recordsView from './recordsView.vue'
export default {
mixins: [mixinsList],
components: {
recordsView,
},
data() {
return {
keyword: '',
// todo
mixinsListApi: '',
}
},
onLoad({ search }) {
if (search) {
this.keyword = search
this.queryParams.title = search
}
this.getData()
},
methods: {
// todo: delete
getData() {
this.list = [
{
id: '001',
name: '呼伦贝尔6/8日丨经典or环线 双套餐可选',
children: [
{
id: '0011',
icon: '/static/image/temp-47.png',
label: '言值认证',
createTime: '2025-07-12',
},
{
id: '0012',
icon: '/static/image/temp-48.png',
label: '国际旅行',
createTime: '2025-07-12',
},
{
id: '0013',
icon: '/static/image/temp-48.png',
label: '萌新毕业证',
createTime: '2025-07-12',
},
],
},
{
id: '002',
name: '新疆天山行7/9日丨醉美伊犁&吐鲁番双套餐新疆天山行7/9日丨醉美伊犁&吐鲁番双套餐',
children: [
{
id: '0011',
icon: '/static/image/temp-48.png',
label: '言值认证',
createTime: '2025-05-15',
},
],
},
{
id: '003',
name: '呼伦贝尔6/8日丨经典or环线 双套餐可选',
children: [
{
id: '0011',
icon: '/static/image/temp-47.png',
label: '言值认证',
createTime: '2025-07-12',
},
{
id: '0012',
icon: '/static/image/temp-48.png',
label: '国际旅行',
createTime: '2025-07-12',
},
],
},
{
id: '004',
name: '新丝路到敦煌7日丨甘青轻松穿越,沙漠+草原',
children: [
{
id: '0012',
icon: '/static/image/temp-48.png',
label: '国际旅行',
createTime: '2025-07-12',
},
{
id: '0013',
icon: '/static/image/temp-47.png',
label: '萌新毕业证',
createTime: '2025-07-12',
},
],
},
]
},
},
}
</script>
<style scoped lang="scss">
.summary {
padding: 16rpx 72rpx 32rpx 64rpx;
justify-content: space-between;
font-family: PingFang SC;
font-weight: 400;
line-height: 1.4;
.info {
.title {
font-size: 32rpx;
font-weight: 600;
color: #000000;
.highlight {
margin: 0 8rpx;
color: $uni-color;
}
}
.tag {
margin-top: 4rpx;
display: inline-block;
padding: 4rpx 16rpx;
font-size: 26rpx;
color: #21607D;
background: #DBF4FF;
border-radius: 22rpx;
}
}
.icon {
width: 160rpx;
height: auto;
.img {
width: 100%;
height: auto;
}
}
}
</style>

+ 112
- 0
pages_order/growing/achievement/recordsView.vue View File

@ -0,0 +1,112 @@
<template>
<view class="records__view">
<uv-steps direction="column">
<uv-steps-item v-for="(record, index) in list" :key="record.id">
<template #icon>
<view class="flex mark is-active" v-if="index === 0">
<image class="icon" src="@/static/image/icon-mark-highlight.png" mode="widthFix"></image>
</view>
<view class="flex mark" v-else>
<image class="icon" src="@/static/image/icon-mark.png" mode="widthFix"></image>
</view>
</template>
<template #title>
<view class="title">{{ record.name }}</view>
</template>
<template #desc>
<view class="content">
<view class="list">
<view class="flex flex-column list-item" v-for="(child, cIdx) in record.children" :key="cIdx">
<image class="list-item-icon" :src="child.icon" mode="aspectFill"></image>
<view class="list-item-title">{{ child.label }}</view>
<view class="list-item-desc">{{ child.createTime }}</view>
</view>
</view>
</view>
</template>
</uv-steps-item>
</uv-steps>
</view>
</template>
<script>
export default {
props: {
list: {
type: Array,
default() {
return []
}
}
},
}
</script>
<style scoped lang="scss">
.records__view {
padding-left: 40rpx;
}
.mark {
margin-top: 8rpx;
width: 36rpx;
height: 36rpx;
background: #F3F3F3;
border-radius: 50%;
.icon {
width: 24rpx;
height: auto;
}
&.is-active {
background: linear-gradient(to right, #21FEEC, #019AF9);
}
}
.title {
font-family: PingFang SC;
font-size: 30rpx;
font-weight: 500;
line-height: 1.4;
color: #191919;
}
.content {
width: calc(100vw - 92rpx);
}
.list {
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
font-family: PingFang SC;
line-height: 1.4;
&-item {
flex: none;
row-gap: 4rpx;
width: 206rpx;
height: 226rpx;
&-icon {
width: 110rpx;
height: 110rpx;
}
&-title {
font-size: 28rpx;
font-weight: 500;
color: #080808;
}
&-desc {
font-size: 26rpx;
font-weight: 400;
color: #999999;
}
}
}
</style>

+ 277
- 0
pages_order/growing/activity/index.vue View File

@ -0,0 +1,277 @@
<template>
<view class="page__view highlight">
<!-- 导航栏 -->
<navbar title="活动详情" leftClick @leftClick="$utils.navigateBack" />
<view class="tabs">
<uv-tabs
:list="tabs"
:scrollable="false"
lineColor="#00A9FF"
lineWidth="48rpx"
lineHeight="4rpx"
:activeStyle="{
'font-family': 'PingFang SC',
'font-weight': 500,
'font-size': '32rpx',
'line-height': 1.4,
'color': '#00A9FF',
}"
:inactiveStyle="{
'font-family': 'PingFang SC',
'font-weight': 400,
'font-size': '32rpx',
'line-height': 1.4,
'color': '#191919',
}"
@click="clickTabs"
></uv-tabs>
</view>
<scroll-view type="custom" scroll-y="true" :scroll-into-view="scrollIntoView" class="scroll-view">
<view class="scroll-view-content">
<view class="cover-img">
<image class="img" src="@/static/image/temp-20.png" mode="aspectFill"></image>
</view>
<view class="section" id="highlights">
<view class="flex section-header">
<view class="flex">
<view class="flex icon">
<image class="img" src="@/static/image/icon-mark.png" mode="widthFix"></image>
</view>
<view>活动掠影</view>
</view>
<view class="btn btn-mark">标记有我</view>
</view>
<view class="section-content highlights">
<view class="highlights-item" v-for="(image, idx) in detail.highlights" :key="idx">
<image class="img" :src="image" mode="scaleToFill"></image>
</view>
</view>
</view>
<view class="section" id="thoughts">
<view class="section-header">学员心得</view>
<view class="section-content thoughts">
<view class="card" v-for="item in detail.thoughts" :key="item.id">
<commentCard :data="item" @change="onCommentChange"></commentCard>
</view>
</view>
</view>
<view class="section report" id="report">
<view class="section-header">行后报告</view>
<view class="section-content report">
<!-- todo -->
</view>
</view>
</view>
</scroll-view>
<view class="flex bottom">
<button class="flex btn btn-palin" @click="jumpToPoster">生成海报</button>
<button class="flex btn btn-primary" @click="onApplyEmail">申请邮件</button>
</view>
</view>
</template>
<script>
import commentCard from '@/pages_order/comment/commentCard.vue'
export default {
components: {
commentCard,
},
data() {
return {
tabs: [
{ id: 'highlights', name: '活动掠影' },
{ id: 'thoughts', name: '学员心得' },
{ id: 'report', name: '行后报告' },
],
current: 0,
detail: {},
scrollIntoView: null,
}
},
onLoad() {
this.getData()
},
methods: {
async getData() {
// todo
this.detail = {
highlights: [
'/static/image/temp-38.png',
'/static/image/temp-39.png',
'/static/image/temp-40.png',
'/static/image/temp-41.png',
'/static/image/temp-42.png',
'/static/image/temp-43.png',
'/static/image/temp-44.png',
'/static/image/temp-45.png',
'/static/image/temp-46.png',
],
thoughts: [],
}
},
//tab
clickTabs({ index }) {
this.current = index
this.scrollIntoView = this.tabs[this.current].id
},
onCommentChange() {
// todo: refresh comment list
},
jumpToPoster() {
// todo
},
onApplyEmail() {
// todo
},
},
}
</script>
<style scoped lang="scss">
.tabs {
background: #D8F2FF;
}
.scroll-view {
$tab-height: 44px;
$bottom-height: 73px;
height: calc(100vh - env(safe-area-inset-bottom) - #{$bottom-height} - #{$tab-height} - #{$navbar-height} - var(--status-bar-height) - 20rpx);
&-content {
padding: 32rpx;
}
}
.cover-img {
width: 100%;
height: 348rpx;
border-radius: 12rpx;
overflow: hidden;
.img {
width: 100%;
height: 100%;
}
}
.section {
margin-top: 32rpx;
&-header {
justify-content: space-between;
font-size: 36rpx;
font-weight: 600;
color: #080808;
.icon {
margin-right: 24rpx;
width: 36rpx;
height: 36rpx;
background: #080808;
border-radius: 50%;
overflow: hidden;
.img {
width: 24rpx;
height: auto;
}
}
.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;
}
}
&-content {
}
}
.highlights {
margin-top: 18rpx;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
&-item {
min-width: 0;
border: 2rpx solid #CDCDCD;
border-radius: 12rpx;
overflow: hidden;
.img {
width: 100%;
height: 304rpx;
}
}
}
.thoughts {
margin-top: 24rpx;
.card + .card {
margin-top: 24rpx;
}
}
.report {
margin-top: 24rpx;
}
.bottom {
position: fixed;
left: 0;
bottom: 0;
z-index: 999;
justify-content: space-between;
column-gap: 32rpx;
width: 100vw;
padding: 32rpx 40rpx;
padding-bottom: calc(env(safe-area-inset-bottom) + 32rpx);
background: #FFFFFF;
box-sizing: border-box;
.btn {
flex: 1;
font-size: 36rpx;
font-weight: 500;
border-radius: 41rpx;
line-height: 1.4;
&-palin {
padding: 14rpx 0;
color: #252545;
border: 2rpx solid #252545;
}
&-primary {
padding: 14rpx 0;
color: #FFFFFF;
background: linear-gradient(to right, #21FEEC, #019AF9);
border: 2rpx solid #00A9FF;
}
}
}
</style>

+ 261
- 0
pages_order/growing/activity/markPopup.vue View File

@ -0,0 +1,261 @@
<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="onSubmit">提交</button>
</view>
</view>
</uv-popup>
</view>
</template>
<script>
export default {
data() {
return {
form: {
image: null,
},
rules: {
'content': {
type: 'image',
required: true,
message: '请上传图片',
},
},
}
},
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 onSubmit() {
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>

+ 353
- 0
pages_order/growing/activity/recordFormPopup.vue View File

@ -0,0 +1,353 @@
<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="tripNum" :customStyle="formItemStyle">
<view class="flex row">
<view class="form-item-label">行程</view>
<view class="form-item-content">
<formRate v-model="form.tripNum"></formRate>
</view>
</view>
</uv-form-item>
</view>
<view class="form-item">
<uv-form-item prop="spotNum" :customStyle="formItemStyle">
<view class="flex row">
<view class="form-item-label">景点</view>
<view class="form-item-content">
<formRate v-model="form.spotNum"></formRate>
</view>
</view>
</uv-form-item>
</view>
<view class="form-item">
<uv-form-item prop="mentorNum" :customStyle="formItemStyle">
<view class="flex row">
<view class="form-item-label">导师</view>
<view class="form-item-content">
<formRate v-model="form.mentorNum"></formRate>
</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>
<view class="form-item" v-for="(item, index) in questions" :key="item.id">
<uv-form-item :prop="`texts[${index}]`" :customStyle="formItemStyle">
<view class="form-item-label">
<image class="icon" src="@/static/image/icon-require.png" mode="widthFix"></image>
{{ item.label }}
</view>
<view class="form-item-content">
<formTextarea v-model="form.texts[index]"></formTextarea>
</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 reloateProjectPopup from './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,
tripNum: null,
spotNum: null,
mentorNum: null,
images: [],
texts: [],
},
rules: {
// todo
},
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() {
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
},
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>

+ 121
- 0
pages_order/growing/activity/reloateProjectPopup.vue View File

@ -0,0 +1,121 @@
<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="onConfirm">确认</button>
</view>
<view class="content">
<uv-radio-group
v-model="selectedId"
placement="column"
shape="circle"
size="36rpx"
iconSize="36rpx"
activeColor="#00A9FF"
>
<view class="flex option" v-for="item in options" :key="item.id">
<view class="radio">
<uv-radio :name="item.id"></uv-radio>
</view>
<view class="text-ellipsis">
{{ item.name }}
</view>
</view>
</uv-radio-group>
</view>
</view>
</uv-popup>
</view>
</template>
<script>
export default {
props: {
options: {
type: Array,
default() {
return []
}
},
},
data() {
return {
selectedId: null,
}
},
onLoad() {},
methods: {
async open(projectId) {
this.selectedId = projectId
this.$refs.popup.open()
},
close() {
this.$refs.popup.close()
},
onConfirm() {
this.$emit('confirm', this.selectedId)
this.close()
},
},
}
</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: $uni-color;
position: absolute;
top: 26rpx;
right: 40rpx;
}
}
.content {
padding: 8rpx 40rpx;
}
.option {
justify-content: flex-start;
column-gap: 16rpx;
padding: 32rpx 0;
font-family: PingFang SC;
font-size: 32rpx;
font-weight: 400;
line-height: 1.4;
color: #181818;
border-bottom: 2rpx solid #EEEEEE;
}
</style>

+ 171
- 0
pages_order/growing/activity/search.vue View File

@ -0,0 +1,171 @@
<template>
<view class="page__view">
<!-- 导航栏 -->
<navbar title="搜索结果" leftClick @leftClick="$utils.navigateBack" bgColor="transparent" color="#191919" />
<!-- 搜索栏 -->
<view class="flex search">
<uv-search
v-model="keyword"
placeholder="输入关键词搜索"
color="#181818"
bgColor="transparent"
:showAction="true"
@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 class="main">
<sortBar v-model="queryParams.sort" @change="onSortChange"></sortBar>
<view v-if="list.length" class="list">
<recordsView :list="list"></recordsView>
</view>
<template v-else>
<uv-empty mode="list"></uv-empty>
</template>
</view>
</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 {
keyword: '',
queryParams: {
pageNo: 1,
pageSize: 10,
title: '',
sort: 'comprehensive',
},
// todo
mixinsListApi: '',
}
},
onLoad({ search }) {
if (search) {
this.keyword = search
this.queryParams.title = search
}
this.getData()
},
methods: {
// todo: delete
getData() {
this.list = [
{
id: '001',
name: '新疆天山行7/9日丨醉美伊犁&吐鲁番双套餐',
image: [
'/static/image/temp-38.png',
'/static/image/temp-39.png',
'/static/image/temp-40.png',
],
createTime: '2025-07-12',
},
{
id: '002',
name: '仙踪新昌·韩妃江古道|邂逅“江南小桂林”',
image: [
'/static/image/temp-41.png',
'/static/image/temp-42.png',
'/static/image/temp-43.png',
],
createTime: '2025-06-18',
},
{
id: '003',
name: '山水石窟·大佛寺|江南佛窟造像,新昌山水轻徒',
image: [
'/static/image/temp-44.png',
'/static/image/temp-45.png',
'/static/image/temp-46.png',
],
createTime: '2025-06-15',
},
]
},
search() {
this.queryParams.pageNo = 1
this.queryParams.pageSize = 10
this.queryParams.title = this.keyword
this.getData()
},
onSortChange(sort) {
console.log('onSortChange', sort)
},
},
}
</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;
}
/deep/ .uv-search__content {
padding: 12rpx 0;
background: #FFFFFF !important;
border-color: #CFEFFF !important;
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;
}
}
.main {
margin-top: 24rpx;
padding: 0 32rpx 100rpx 32rpx;
}
.content {
margin-top: 24rpx;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16rpx;
}
</style>

+ 107
- 0
pages_order/growing/activity/sortBar.vue View File

@ -0,0 +1,107 @@
<template>
<view class="flex sort" :style="style">
<view :class="['flex', 'sort-item', sort == 'comprehensive' ? 'is-active' : '']" @click="onClickSort('comprehensive')">地点</view>
<view :class="['flex', 'sort-item', ['sale-asc', 'sale-desc'].includes(sort) ? 'is-active' : '']" @click="onClickSort('sale')">
<view>主题</view>
<view class="sort-item-icon">
<uv-icon v-if="sort == 'sale-asc'" name="arrow-up-fill" color="#00A9FF" size="16rpx" :bold="true"></uv-icon>
<uv-icon v-else-if="sort == 'sale-desc'" name="arrow-down-fill" color="#00A9FF" size="16rpx" :bold="true"></uv-icon>
<image v-else style="width: 8rpx; height: auto;" src="/static/image/icon-sort.png" mode="widthFix"></image>
</view>
</view>
<view :class="['flex', 'sort-item', ['price-asc', 'price-desc'].includes(sort) ? 'is-active' : '']" @click="onClickSort('price')">
<view>时间</view>
<view class="sort-item-icon">
<uv-icon v-if="sort == 'price-asc'" name="arrow-up-fill" color="#00A9FF" size="16rpx" :bold="true"></uv-icon>
<uv-icon v-else-if="sort == 'price-desc'" name="arrow-down-fill" color="#00A9FF" size="16rpx" :bold="true"></uv-icon>
<image v-else style="width: 8rpx; height: auto;" src="/static/image/icon-sort.png" mode="widthFix"></image>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
value: {
type: String,
default: 'comprehensive'
},
style: {
type: String,
default: ''
}
},
computed: {
sort: {
set(val) {
this.$emit('input', val)
},
get() {
return this.value
}
}
},
methods: {
onClickSort(key) {
let sort = 'comprehensive'
switch(key) {
case 'comprehensive':
sort = 'comprehensive'
break;
case 'sale':
if (this.sort == 'sale-desc') {
sort = 'sale-asc'
} else {
sort = 'sale-desc'
}
break;
case 'price':
if (this.sort == 'price-desc') {
sort = 'price-asc'
} else {
sort = 'price-desc'
}
break;
default:
break;
}
this.sort = sort
this.$emit('change', sort)
},
},
}
</script>
<style scoped lang="scss">
.sort {
width: 100%;
justify-content: space-between;
&-item {
padding: 12rpx 32rpx;
font-size: 28rpx;
line-height: 1.5;
color: #191919;
column-gap: 4rpx;
&.is-active {
font-weight: 600;
color: #00A9FF;
}
&-icon {
width: 32rpx;
display: inline-flex;
align-items: center;
justify-content: center;
}
}
}
</style>

+ 95
- 0
pages_order/member/memberCard.vue View File

@ -0,0 +1,95 @@
<template>
<view class="flex card">
<view class="radio" v-if="showRadio">
<uv-radio :name="data.id"></uv-radio>
</view>
<view class="info">
<view class="title">{{ typeDesc }}</view>
<view class="row">
<view class="row-label">绑定人</view>
<view class="row-content">{{ data.name }}</view>
</view>
<view class="row">
<view class="row-label">申请人ID</view>
<view class="row-content">{{ data.userId }}</view>
</view>
</view>
</view>
</template>
<script>
const TYPE_AND_DESC_MAPPING = {
0: '学生',
1: '家长',
}
export default {
props: {
data: {
type: Object,
default() {
return {}
}
},
value: {
type: String,
default: null,
},
showRadio: {
type: Boolean,
default: false
},
},
data() {
return {
}
},
computed: {
typeDesc() {
const { type } = this.data
return TYPE_AND_DESC_MAPPING[type] || ''
},
},
methods: {
},
}
</script>
<style scoped lang="scss">
.card {
justify-content: flex-start;
column-gap: 24rpx;
padding: 40rpx 32rpx;
font-family: PingFang SC;
font-weight: 400;
line-height: 1.4;
background: #FFFFFF;
border-radius: 32rpx;
}
.title {
font-size: 32rpx;
font-weight: 500;
color: #181818;
}
.row {
margin-top: 16rpx;
display: flex;
align-items: center;
justify-content: flex-start;
column-gap: 4rpx;
font-size: 28rpx;
&-label {
color: #8B8B8B;
}
&-content {
color: #393939;
}
}
</style>

+ 139
- 0
pages_order/member/switch.vue View File

@ -0,0 +1,139 @@
<template>
<view class="page__view">
<navbar title="我的优惠券" leftClick @leftClick="$utils.navigateBack" color="#191919" bgColor="#FFFFFF" />
<view class="list">
<uv-radio-group
v-model="selectedId"
placement="column"
shape="circle"
size="36rpx"
iconSize="36rpx"
activeColor="#00A9FF"
@change="onRadioChange"
>
<view class="list-item" v-for="item in list" :key="item.id">
<memberCard
:data="item"
:showRadio="true"
></memberCard>
</view>
</uv-radio-group>
</view>
</view>
</template>
<script>
import { mapState } from 'vuex'
import mixinsList from '@/mixins/list.js'
import memberCard from './memberCard.vue'
export default {
mixins: [mixinsList],
components: {
memberCard,
},
data() {
return {
// todo: check key
mixinsListApi: '',
queryParams: {
pageNo: 1,
pageSize: 10,
},
selectedId: null,
}
},
computed: {
...mapState(['memberInfo']),
},
onLoad(arg) {
if (this.memberInfo?.id) {
this.memberInfo = this.memberInfo.id
}
this.getData()
},
onUnload() {
if (!this.selectedId) {
this.$store.commit('setMemberInfo', null)
return
}
const target = this.list.find(item => item.id === this.selectedId)
this.$store.commit('setMemberInfo', target)
},
methods: {
// todo: delete
getData() {
this.list = [
{
id: '001',
name: '周小艺',
userId: '15558661691',
type: 0,
},
{
id: '002',
name: '周小艺',
userId: '15558661691',
type: 0,
},
{
id: '003',
name: '周小艺',
userId: '15558661691',
type: 1,
},
{
id: '004',
name: '周小艺',
userId: '15558661691',
type: 0,
},
{
id: '005',
name: '周小艺',
userId: '15558661691',
type: 0,
},
]
},
onSelect(id) {
console.log('onSelect', id)
this.selectedId = id
},
onRadioChange(e) {
console.log('onRadioChange', e)
},
},
}
</script>
<style scoped lang="scss">
.page__view {
width: 100vw;
min-height: 100vh;
background-color: $uni-bg-color;
position: relative;
}
.list {
padding: 32rpx 40rpx;
&-item {
& + & {
margin-top: 24rpx;
}
}
}
</style>

+ 0
- 1
pages_order/order/orderConfirm/infoPopup.vue View File

@ -67,7 +67,6 @@
</view>
</view>
</uv-popup>
</view>
</template>


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

Before After
Width: 48  |  Height: 48  |  Size: 541 B

BIN
static/image/icon-like-active.png View File

Before After
Width: 48  |  Height: 48  |  Size: 565 B

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

Before After
Width: 48  |  Height: 48  |  Size: 895 B

BIN
static/image/icon-mark-highlight.png View File

Before After
Width: 36  |  Height: 37  |  Size: 916 B

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

Before After
Width: 36  |  Height: 37  |  Size: 878 B

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

Before After
Width: 41  |  Height: 41  |  Size: 1.3 KiB

BIN
static/image/temp-37.png View File

Before After
Width: 1079  |  Height: 2326  |  Size: 1.5 MiB

BIN
static/image/temp-38.png View File

Before After
Width: 495  |  Height: 657  |  Size: 372 KiB

BIN
static/image/temp-39.png View File

Before After
Width: 495  |  Height: 660  |  Size: 449 KiB

BIN
static/image/temp-40.png View File

Before After
Width: 495  |  Height: 657  |  Size: 529 KiB

BIN
static/image/temp-41.png View File

Before After
Width: 659  |  Height: 495  |  Size: 638 KiB

BIN
static/image/temp-42.png View File

Before After
Width: 659  |  Height: 495  |  Size: 626 KiB

BIN
static/image/temp-43.png View File

Before After
Width: 495  |  Height: 660  |  Size: 503 KiB

BIN
static/image/temp-44.png View File

Before After
Width: 440  |  Height: 660  |  Size: 498 KiB

BIN
static/image/temp-45.png View File

Before After
Width: 659  |  Height: 495  |  Size: 401 KiB

BIN
static/image/temp-46.png View File

Before After
Width: 659  |  Height: 495  |  Size: 441 KiB

BIN
static/image/temp-47.png View File

Before After
Width: 1328  |  Height: 1328  |  Size: 2.1 MiB

BIN
static/image/temp-48.png View File

Before After
Width: 1328  |  Height: 1328  |  Size: 1.9 MiB

BIN
static/image/temp-49.png View File

Before After
Width: 480  |  Height: 480  |  Size: 217 KiB

+ 4
- 0
store/store.js View File

@ -15,6 +15,7 @@ const store = new Vuex.Store({
travelerList: null,
orderInfo: null,
couponInfo: null,
memberInfo: null,
},
getters: {
// 角色 true为水洗店 false为酒店 : 身份判断如果不需要,可以删除
@ -116,6 +117,9 @@ const store = new Vuex.Store({
setCouponInfo(state, data) {
state.couponInfo = data
},
setMemberInfo(state, data) {
state.memberInfo = data
},
},
actions: {
async collect(state, id) {


Loading…
Cancel
Save