Browse Source

feat: 接口对接;

pull/6/head
Fox-33 2 months ago
parent
commit
f54d071dad
36 changed files with 1209 additions and 736 deletions
  1. +4
    -1
      api/api.js
  2. +10
    -0
      api/model/index.js
  3. +20
    -0
      api/model/medal.js
  4. +5
    -0
      api/model/partner.js
  5. +4
    -0
      common.scss
  6. +244
    -0
      components/base/posterPopup.vue
  7. +2
    -1
      components/growing/recordsView.vue
  8. +26
    -13
      components/growing/userCard.vue
  9. +47
    -66
      components/home/categoryView.vue
  10. +1
    -0
      components/home/productView.vue
  11. +0
    -4
      components/home/recommendView.vue
  12. +5
    -5
      components/partner/posterPopup copy.vue
  13. +17
    -21
      components/product/productCard.vue
  14. +6
    -0
      mixins/list.js
  15. +3
    -0
      pages.json
  16. +132
    -241
      pages/index/category.vue
  17. +26
    -10
      pages/index/growing.vue
  18. +18
    -2
      pages/index/index.vue
  19. +28
    -106
      pages/index/partner.vue
  20. +4
    -0
      pages_order/article/search.vue
  21. +141
    -0
      pages_order/auth/roleChoose.vue
  22. +1
    -2
      pages_order/auth/wxUserInfo.vue
  23. +42
    -29
      pages_order/comment/recordFormPopup.vue
  24. +21
    -77
      pages_order/growing/achievement/index.vue
  25. +12
    -2
      pages_order/growing/achievement/recordsView.vue
  26. +210
    -0
      pages_order/growing/activity/commentCard.vue
  27. +50
    -30
      pages_order/growing/activity/index.vue
  28. +42
    -42
      pages_order/growing/activity/search.vue
  29. +7
    -7
      pages_order/growing/activity/sortBar.vue
  30. +4
    -50
      pages_order/order/components/orderInfoView.vue
  31. +26
    -1
      pages_order/order/orderConfirm/index.vue
  32. +5
    -9
      pages_order/order/orderConfirm/peopleNumberInput.vue
  33. +2
    -2
      pages_order/partner/team.vue
  34. +1
    -1
      pages_order/product/commentList.vue
  35. +8
    -8
      pages_order/product/productDetail.vue
  36. +35
    -6
      store/store.js

+ 4
- 1
api/api.js View File

@ -5,7 +5,7 @@ import utils from '../utils/utils.js'
let limit = {}
let debounce = {}
const models = ['login', 'index', 'image', 'activity', 'coupon', 'order', 'comment', 'bind', 'experience', 'partner', 'info', 'notice']
const models = ['login', 'index', 'image', 'activity', 'coupon', 'order', 'comment', 'medal', 'bind', 'experience', 'partner', 'info', 'notice']
const config = {
// 示例
@ -19,6 +19,9 @@ const config = {
queryPeriodList : {url : '/config/queryPeriodList', method : 'GET'},
queryCommentOptionList : {url : '/config/queryCommentOptionList', method : 'GET'},
queryExperienceQuestionList : {url : '/config/queryExperienceQuestionList', method : 'GET'},
queryAddressList : {url : '/config/queryAddressList', method : 'GET'},
queryAgeList : {url : '/config/queryAgeList', method : 'GET'},
queryTimeList : {url : '/config/queryTimeList', method : 'GET'},
}


+ 10
- 0
api/model/index.js View File

@ -26,6 +26,16 @@ const api = {
url: '/index/queryPolicyById',
method: 'GET',
},
// 首页-查询研学日记列表
queryJournalList: {
url: '/index/queryJournalList',
method: 'GET',
},
// 首页-查询研学日记详情
queryJournalById: {
url: '/index/queryJournalById',
method: 'GET',
},
}
export default api

+ 20
- 0
api/model/medal.js View File

@ -0,0 +1,20 @@
// 我的成就相关接口
const api = {
// 我的成就-查询勋章列表
queryMedalList: {
url: '/medal/queryMedalList',
method: 'GET',
auth: true,
},
// 我的成就-点亮勋章
lightMedal: {
url: '/medal/lightMedal',
method: 'POST',
auth: true,
limit : 500,
showLoading : true,
},
}
export default api

+ 5
- 0
api/model/partner.js View File

@ -35,6 +35,11 @@ const api = {
url: '/partner/queryIndirectList',
method: 'GET',
},
// 我的团队-查询佣金记录列表
queryCommissionList: {
url: '/partner/queryCommissionList',
method: 'GET',
},
}
export default api

+ 4
- 0
common.scss View File

@ -77,6 +77,10 @@
border: none;
}
.nowrap {
white-space: nowrap;
}
/deep/ .uv-modal__content {
padding: 0 !important;
}


+ 244
- 0
components/base/posterPopup.vue View File

@ -0,0 +1,244 @@
<template>
<uv-popup
ref="popup"
:overlayOpacity="0.6"
mode="center"
bgColor="none"
:zIndex="1000000"
@change="onPopupChange"
>
<view class="popup__view">
<view class="canvas" style="width: 566rpx; height: 1060rpx; overflow: hidden;">
<canvas id="myCanvas" canvas-id="firstCanvas1" type="2d" style="width: 100%; height: 100%;"></canvas>
</view>
<button class="btn" @click="saveImg">
<view class="content">保存到相册</view>
</button>
</view>
</uv-popup>
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {
path: '',
wxCodeImage: '',
baseUrl: 'https://image.hhlm1688.com/',
canvas: {},
retry: 10,
}
},
computed: {
...mapState(['userInfo', 'configList']),
},
async onLoad() {
},
methods: {
open(path) {
this.path = path
this.retry = 10
this.$refs.popup.open();
},
close() {
this.$refs.popup.close();
},
async fetchQrCode() {
try {
this.wxCodeImage = (await this.$fetch('getInviteCode', { path: this.path }))?.url
} catch (err) {
}
},
drawCoverImg(canvas, ctx, x, y, width, height) {
return new Promise(resolve => {
//
const paperImage = canvas.createImage()
console.log('paperImage', paperImage)
paperImage.src = this.configList.poster_image
paperImage.onload = () => {
console.log('paperImage onload')
ctx.drawImage(paperImage, x, y, width, height)
resolve()
}
})
},
drawQrCodeImg(canvas, ctx, x, y, size) {
return new Promise(resolve => {
//
const coderImage = canvas.createImage()
coderImage.src = this.wxCodeImage
coderImage.onload = () => {
console.log('coderImage onload')
ctx.drawImage(coderImage, x, y, size, size)
resolve()
}
})
},
draw() {
wx.createSelectorQuery().in(this)
.select('#myCanvas') // canvasid
.fields({
node: true,
size: true
})
.exec(async (res) => {
console.log('res', res)
if (!res?.[0]?.node) {
if (!this.retry) {
console.log('retry fail')
return
}
console.log('retry')
this.retry -= 1
setTimeout(() => {
this.draw()
}, 200)
return
}
const canvas = res[0].node
//
const ctx = canvas.getContext('2d')
// Canvas
const width = res[0].width
const height = res[0].height
//
const dpr = wx.getWindowInfo().pixelRatio
//dpr
// dpr 2 4
// 3 6
console.log("--dpr", dpr)
canvas.width = width * dpr
canvas.height = height * dpr
let Ratio = canvas.width / 566
this.canvas = canvas
ctx.scale(dpr, dpr)
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = 'transparent'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.save()
let x = 0
let y = 0
let w = 566 * Ratio / dpr
let h = 1060 * Ratio / dpr
await this.drawCoverImg(canvas, ctx, x, y, w, h)
ctx.restore();
ctx.save()
x = 316 * Ratio / dpr
y = 810 * Ratio / dpr
let size = 210 * Ratio / dpr
await this.drawQrCodeImg(canvas, ctx, x, y, size)
uni.hideLoading()
})
},
async init() {
uni.showLoading({
title: '加载中...'
});
await this.fetchQrCode()
uni.hideLoading();
uni.showLoading({
title: "拼命绘画中..."
})
this.draw()
},
saveImg() {
this.$authorize('scope.writePhotosAlbum').then((res) => {
this.imgApi()
})
},
imgApi() {
uni.showLoading({
title: '保存中...'
});
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: this.canvas.width,
height: this.canvas.height,
canvas: this.canvas,
success: (res) => {
let tempFilePath = res.tempFilePath;
this.saveImgToPhone(tempFilePath)
},
fail: (err) => {
console.log('--canvasToTempFilePath--fail', err)
uni.hideLoading();
}
}, this);
},
saveImgToPhone(image) {
/* 获取图片的信息 */
uni.getImageInfo({
src: image,
success: function(image) {
/* 保存图片到手机相册 */
uni.saveImageToPhotosAlbum({
filePath: image.path,
success: function() {
uni.showModal({
title: '保存成功',
content: '图片已成功保存到相册',
showCancel: false
});
},
complete(res) {
console.log(res);
uni.hideLoading();
}
});
}
});
},
onPopupChange(e) {
if (!e.show) {
return
}
this.init()
},
},
}
</script>
<style scoped lang="scss">
.canvas {
border-radius: 48rpx;
}
.btn {
margin-top: 32rpx;
width: 100%;
padding: 22rpx 0;
box-sizing: border-box;
font-family: PingFang SC;
font-weight: 500;
font-size: 36rpx;
line-height: 1.4;
color: #FFFFFF;
background: linear-gradient(to right, #21FEEC, #019AF9);
border: 2rpx solid #00A9FF;
border-radius: 41rpx;
}
</style>

+ 2
- 1
components/growing/recordsView.vue View File

@ -15,7 +15,7 @@
</template>
<template #desc>
<view class="content" @click="onClickActivity(record.id)">
<view class="desc">{{ record.activityTitle || record.activityId_dictText || '' }}</view>
<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="aspectFill"></image>
@ -82,6 +82,7 @@
}
.desc {
padding: 12rpx 0 8rpx 0;
font-family: PingFang SC;
font-size: 28rpx;
font-weight: 400;


+ 26
- 13
components/growing/userCard.vue View File

@ -17,7 +17,7 @@
<view class="flex info">
<view class="avatar">
<image class="img" :src="memberInfo.headImage" mode="scaleToFill"></image>
<view :class="['tag', `tag-0`]">学生</view>
<view :class="['tag', `tag-${memberInfo.role}`]">{{ memberInfo.roleDesc || '' }}</view>
</view>
<view class="flex summary">
<view class="flex flex-column summary-item name">
@ -25,24 +25,17 @@
<view class="summary-item-label">{{ `ID:${memberInfo.id}` }}</view>
</view>
<view class="flex flex-column summary-item" @click="jumpToAchievement">
<view class="summary-item-content">8</view>
<view class="summary-item-content">{{ medalCount }}</view>
<view class="summary-item-label nowrap">成就</view>
</view>
<view class="flex flex-column summary-item">
<view class="summary-item-content">68</view>
<view class="summary-item-content">{{ experienceCount }}</view>
<view class="summary-item-label nowrap">足迹</view>
</view>
</view>
</view>
<view class="flex medal">
<image class="medal-item" src="@/pages_order/static/temp-47.png" mode="widthFix"></image>
<image class="medal-item" src="@/pages_order/static/temp-47.png" mode="widthFix"></image>
<image class="medal-item" src="@/pages_order/static/temp-49.png" mode="widthFix"></image>
<image class="medal-item" src="@/pages_order/static/temp-47.png" mode="widthFix"></image>
<image class="medal-item" src="@/pages_order/static/temp-47.png" mode="widthFix"></image>
<image class="medal-item" src="@/pages_order/static/temp-49.png" mode="widthFix"></image>
<image class="medal-item" src="@/pages_order/static/temp-47.png" mode="widthFix"></image>
<image class="medal-item" src="@/pages_order/static/temp-47.png" mode="widthFix"></image>
<image class="medal-item" v-for="item in medalList" :key="item.id" :src="item.medal.icon1" mode="widthFix"></image>
</view>
</view>
</view>
@ -52,6 +45,22 @@
import { mapState } from 'vuex'
export default {
props: {
medalList: {
type: Array,
default() {
return []
}
},
medalCount: {
type: Number,
default: 0
},
experienceCount: {
type: Number,
default: 0
},
},
data() {
return {
}
@ -63,8 +72,12 @@
...mapState(['memberInfo']),
},
methods: {
getData() {
// todo
async getData() {
try {
await this.$fetch('queryMedalList', { pageNo: 1, pageSize: 1000, isLight: '1', })
} catch (err) {
}
},
onAdd() {
this.$emit('addRecord')


+ 47
- 66
components/home/categoryView.vue View File

@ -1,7 +1,7 @@
<template>
<view class="category">
<view class="flex flex-column category-item" v-for="item in categoryOptions" :key="item.id" @click="onClick(item)">
<image class="img" :src="item.icon" mode="widthFix"></image>
<image class="img" :src="item.icon" mode="aspectFit"></image>
<view>{{ item.label }}</view>
</view>
</view>
@ -18,71 +18,52 @@
this.getData()
},
methods: {
getData() {
async getData() {
// todo: fetch
this.categoryOptions = [
{
id: '001',
icon: '/static/image/temp-1.png',
label: '主题研学',
path: `/pages/index/category?categoryId=1962345709817106434`,
},
{
id: '002',
icon: '/static/image/temp-2.png',
label: '社会实践',
path: `/pages/index/category?categoryId=1962346300198948866`,
},
{
id: '003',
icon: '/static/image/temp-3.png',
label: '研学交流',
path: `/pages/index/category?categoryId=1962346769759670273`,
},
{
id: '004',
icon: '/static/image/temp-4.png',
label: '周末研学',
path: `/pages/index/category?categoryId=1962346834884628481`,
},
{
id: '005',
icon: '/static/image/temp-5.png',
label: '假期专项',
path: `/pages/index/category?categoryId=1962346960097185793`,
},
{
id: '006',
icon: '/static/image/temp-6.png',
label: '研学日记',
},
{
id: '007',
icon: '/static/image/temp-7.png',
label: '研学政策',
path: `/pages_order/article/search?api=queryPolicyList&title=研学政策`,
},
{
id: '008',
icon: '/static/image/temp-8.png',
label: '本地研学',
path: `/pages/index/category?categoryId=1962347024639135745`,
},
{
id: '009',
icon: '/static/image/temp-9.png',
label: '公司动态',
path: `/pages_order/article/search?api=queryNewsList&title=公司动态`,
},
{
id: '010',
icon: '/static/image/temp-10.png',
label: '全部',
path: `/pages/index/category`,
},
]
try {
const categorys = (await this.$fetch('queryCategoryList', { pageSize: 6, isShow: '1' }))?.records?.map(item => {
const { id, icon, title } = item
return {
id,
icon,
label: title,
path: `/pages/index/category?categoryId=${id}`,
}
})
this.categoryOptions = [
...categorys.slice(0, 5),
{
id: '006',
icon: '/static/image/temp-6.png',
label: '研学日记',
path: `/pages_order/article/search?api=queryJournalList&title=研学日记`,
},
{
id: '007',
icon: '/static/image/temp-7.png',
label: '研学政策',
path: `/pages_order/article/search?api=queryPolicyList&title=研学政策`,
},
categorys[5],
{
id: '009',
icon: '/static/image/temp-9.png',
label: '公司动态',
path: `/pages_order/article/search?api=queryNewsList&title=公司动态`,
},
{
id: '010',
icon: '/static/image/temp-10.png',
label: '全部',
path: `/pages/index/category`,
},
]
} catch(err) {
}
},
onClick(target) {
@ -121,7 +102,7 @@
.img {
width: 72rpx;
height: auto;
height: 72rpx;
}
}
}


+ 1
- 0
components/home/productView.vue View File

@ -35,6 +35,7 @@
<productCard
:data="item"
size="small"
@collect="$emit('collect')"
></productCard>
</view>
</view>


+ 0
- 4
components/home/recommendView.vue View File

@ -22,9 +22,6 @@
},
methods: {
getData() {
// todo: fetch
this.recommendList = [
{
id: '001',
@ -49,7 +46,6 @@
path: `/pages_order/product/search?isNew=1&title=新上线路`,
},
]
},
onClick(target) {
const { path } = target


components/partner/posterPopup.vue → components/partner/posterPopup copy.vue View File

@ -45,12 +45,12 @@
close() {
this.$refs.popup.close();
},
async fetchQrCode(path) {
// todo: delete
this.wxCodeImage = 'https://uploadfile.bizhizu.cn/up/e3/64/e0/e364e0f7f6af11f11abdafc22d17b15c.jpg'
return
async fetchQrCode() {
// // todo: delete
// this.wxCodeImage = 'https://uploadfile.bizhizu.cn/up/e3/64/e0/e364e0f7f6af11f11abdafc22d17b15c.jpg'
// return
try {
const path = `pages/index/index?userId=${this.userInfo.id}`
const path = `pages/index/index?shareId=${this.userInfo.id}`
this.wxCodeImage = (await this.$fetch('getInviteCode', { path }))?.url
} catch (err) {

+ 17
- 21
components/product/productCard.vue View File

@ -49,11 +49,6 @@
return {}
}
},
// todo: fetch
isCollected: {
type: Boolean,
default: false,
},
size: {
type: String,
default: 'normal' // normal | small
@ -64,6 +59,7 @@
isMove: false,
startClientX: null,
displayX: 0,
collectBtnVisible: false,
}
},
computed: {
@ -84,6 +80,9 @@
let frac = this.data.priceDiscount % this.priceInt
return frac > 0 ? frac.toFixed(2).slice(1) : ''
},
isCollected() {
return this.data.isCollection == '1'
},
collectBtnWidth() {
return this.isCollected ? 80 : 56
},
@ -98,7 +97,7 @@
const translateX = 100 - display
return `width: ${width}px; transform: translateX(${translateX}%); background: ${background};`
}
},
},
methods: {
onTouchstart(e) {
@ -120,11 +119,21 @@
this.isMove = true
},
onTouchend() {
console.log('displayX', this.displayX, this.collectBtnWidth, this.displayX < this.collectBtnWidth)
if (!this.isMove && !this.collectBtnVisible) {
this.onRegistrate()
}
if (this.displayX < this.collectBtnWidth) {
this.displayX = 0
}
if (this.displayX) {
this.collectBtnVisible = true
} else {
this.collectBtnVisible = false
}
this.isMove = false
},
showCollectBtn() {
@ -134,23 +143,10 @@
this.displayX = 0
},
async onCollect() {
console.log('onCollect')
try {
let succ
if (this.isCollected) {
// todo: fetch cancel collect
succ = true
uni.showToast({
icon: 'success',
title: '已移除收藏',
});
} else {
succ = await this.$store.dispatch('collect', this.data.id)
}
let succ = await this.$store.dispatch('collect', this.data.id)
succ && this.hiddenCollectBtn()
this.$emit('collect', !this.isCollected)


+ 6
- 0
mixins/list.js View File

@ -61,6 +61,12 @@ export default {
this.total = res.result.total || res.result.length
// 调用数据加载完成的回调
this.getDataThen && this.getDataThen(res.result.records, res.result.total, res.result)
} else {
// 更新列表数据
this[this.mixinsListKey || 'list'] = []
// 更新总数
this.total = 0
error(res)
}
})
})


+ 3
- 0
pages.json View File

@ -52,6 +52,9 @@
{
"path": "auth/wxUserInfo"
},
{
"path": "auth/roleChoose"
},
{
"path": "auth/loginAndRegisterAndForgetPassword"
},


+ 132
- 241
pages/index/category.vue View File

@ -22,7 +22,7 @@
<view class="flex range price">
<view class="range-item">
<uv-input
v-model="startPrice"
v-model="priceLow"
type="number"
inputAlign="center"
placeholder="开始价格"
@ -36,12 +36,14 @@
}"
fontSize="28rpx"
:clearable="true"
@confirm="onStartPriceChange"
@clear="onStartPriceChange(null)"
></uv-input>
</view>
<view class="split"></view>
<view class="range-item">
<uv-input
v-model="endPrice"
v-model="priceHigh"
type="number"
inputAlign="center"
placeholder="结束价格"
@ -55,6 +57,8 @@
}"
fontSize="28rpx"
:clearable="true"
@confirm="onEndPriceChange"
@clear="onEndPriceChange(null)"
></uv-input>
</view>
</view>
@ -62,22 +66,22 @@
<template v-else-if="filter.key === 'time'">
<view class="flex range time">
<view class="range-item" @click="openStartDatePicker">
{{ startDate ? $dayjs(startDate).format('YYYY-MM-DD') : '开始日期' }}
<button v-if="startDate" class="btn btn-clear" @click.stop="onClearStartDate">
{{ dateLow ? $dayjs(dateLow).format('YYYY-MM-DD') : '开始日期' }}
<button v-if="dateLow" class="btn btn-clear" @click.stop="onClearStartDate">
<uv-icon name="close-circle" color="#B5B5B5" size="28rpx"></uv-icon>
</button>
</view>
<view class="split"></view>
<view class="range-item" @click="openEndDatePicker">
{{ endDate ? $dayjs(endDate).format('YYYY-MM-DD') : '结束日期' }}
<button v-if="endDate" class="btn btn-clear" @click.stop="onClearEndDate">
{{ dateHigh ? $dayjs(dateHigh).format('YYYY-MM-DD') : '结束日期' }}
<button v-if="dateHigh" class="btn btn-clear" @click.stop="onClearEndDate">
<uv-icon name="close-circle" color="#B5B5B5" size="28rpx"></uv-icon>
</button>
</view>
</view>
<uv-datetime-picker
ref="startDatePicker"
v-model="startDate"
v-model="dateLow"
mode="date"
title="开始日期"
confirmColor="#00A9FF"
@ -87,12 +91,12 @@
></uv-datetime-picker>
<uv-datetime-picker
ref="endDatePicker"
v-model="endDate"
v-model="dateHigh"
mode="date"
title="结束日期"
confirmColor="#00A9FF"
round="32rpx"
:minDate="startDate || minTime"
:minDate="dateLow || minTime"
@confirm="onEndDateChange"
></uv-datetime-picker>
</template>
@ -187,19 +191,19 @@
data() {
return {
current: 0,
startPrice: null,
endPrice: null,
startDate: null,
endDate: null,
priceLow: null,
priceHigh: null,
dateLow: null,
dateHigh: null,
minTime: new Date().getTime(),
queryParams: {
pageNo: 1,
pageSize: 1000,
// todo
sort: 'comprehensive',
},
categoryList: [],
filters: [],
addressOptionsConfig: {},
isFold: true,
}
},
@ -230,167 +234,52 @@
} catch(err) {
this.categoryList = []
}
return
this.categoryList = [
{
"key": "1962345168240185345",
"title": "国际游",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345225345634305",
"title": "夏令营",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345290571255810",
"title": "周末营",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345372007862273",
"title": "周边游",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345497681793025",
"title": "定制游",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345589524467714",
"title": "周末活动",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345642188148737",
"title": "亲子活动",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962345709817106434",
"title": "主题研学",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962346300198948866",
"title": "社会实践",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962346769759670273",
"title": "研学交流",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962346834884628481",
"title": "周末研学",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962346960097185793",
"title": "假期专享",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
},
{
"key": "1962347024639135745",
"title": "本地研学",
"icon": null,
"parentId": "0",
"value": null,
"code": null,
"children": null,
"leaf": true
}
].map(item => {
const { key, title } = item
return {
id: key,
name: title,
children: []
}
})
},
async fetchFilters() {
let fetchs = [
this.$fetch('queryAddressList', { pageNo: 1, pageSize: 1000, pid: '0' }),
this.$fetch('queryAgeList', { pageNo: 1, pageSize: 1000 }),
this.$fetch('queryTimeList', { pageNo: 1, pageSize: 1000 }),
]
const results = (await Promise.allSettled(fetchs))
.map(res => {
return res.value.records.map(item => {
return {
id: item.id,
label: item.title,
}
})
})
console.log('results', results)
fetchs = results[0].map(item => {
return this.$fetch('queryAddressList', { pageNo: 1, pageSize: 1000, pid: item.id })
})
const addressResults = (await Promise.allSettled(fetchs))
.map(res => {
return res.value.records.map(item => {
return {
id: item.id,
label: item.title,
}
})
})
console.log('addressResults', addressResults)
const addressOptionsConfig = addressResults.reduce((obj, records, index) => {
obj[results[0][index].id] = records
return obj
}, {})
this.addressOptionsConfig = addressOptionsConfig
console.log('addressOptionsConfig', addressOptionsConfig)
this.filters = [
{
id: '001',
key: 'frontier',
label: '国境',
options: [
{
id: '00101',
label: '国内',
},
{
id: '00102',
label: '国外',
},
],
options: results[0],
},
{
id: '002',
@ -400,30 +289,7 @@
{
label: '全部',
},
{
id: '00201',
label: '上海',
},
{
id: '00202',
label: '北京',
},
{
id: '00203',
label: '浙江省',
},
{
id: '00204',
label: '广东省',
},
{
id: '00205',
label: '广西省',
},
{
id: '00206',
label: '云南省',
},
...addressOptionsConfig[results[0][0].id]
],
},
{
@ -434,22 +300,7 @@
{
label: '全部',
},
{
id: '00301',
label: '6-10岁',
},
{
id: '00302',
label: '11-14岁',
},
{
id: '00303',
label: '15-16岁',
},
{
id: '00304',
label: '17-18岁',
},
...results[1]
],
},
{
@ -460,22 +311,7 @@
{
label: '全部',
},
{
id: '00401',
label: '1日',
},
{
id: '00402',
label: '多日',
},
{
id: '00403',
label: '寒假',
},
{
id: '00404',
label: '暑假',
},
...results[2],
],
},
{
@ -489,6 +325,7 @@
label: '出发日期',
},
]
console.log('filters', this.filters)
this.filters.forEach(item => {
const { key, options } = item
@ -500,11 +337,39 @@
this.queryParams[key] = options[0].id
})
// todo: fetch
},
async queryProductList(categoryId) {
try {
return (await this.$fetch('queryActivityList', { ...this.queryParams, categoryId }))?.records || []
const {
frontier,
addressId,
sort,
...params
} = this.queryParams
params.addressId = addressId || frontier
params.categoryId = categoryId
switch(sort) {
// (saleOrder)0- 1-
case 'sale-desc': // -
params.saleOrder = '0'
break
case 'sale-asc': // -
params.saleOrder = '1'
break
// (priceOrder)0- 1-
case 'price-desc': // -
params.priceOrder = '0'
break
case 'price-asc': // -
params.priceOrder = '1'
break
default:
break
}
return (await this.$fetch('queryActivityList', params))?.records || []
} catch (err) {
return []
}
@ -532,6 +397,33 @@
} else {
delete this.queryParams[key]
}
if (key === 'frontier') {
this.filters[1].options = [
{
label: '全部',
},
...this.addressOptionsConfig[val]
]
delete this.queryParams.addressId
}
this.initList()
},
onStartPriceChange(value) {
if (value) {
this.queryParams.priceLow = value
} else {
delete this.queryParams.priceLow
}
this.initList()
},
onEndPriceChange(value) {
if (value) {
this.queryParams.priceHigh = value
} else {
delete this.queryParams.priceHigh
}
this.initList()
},
openStartDatePicker() {
@ -540,21 +432,21 @@
onStartDateChange(e) {
const date = e.value
this.queryParams.startDate = date
this.queryParams.dateLow = $dayjs(date).format('YYYY-MM-DD')
const { endDate } = this.queryParams
const { dateHigh } = this.queryParams
if (endDate && this.$dayjs(date).isAfter(endDate, 'day')) {
this.endDate = null
delete this.queryParams.endDate
if (dateHigh && this.$dayjs(date).isAfter(dateHigh, 'day')) {
this.dateHigh = null
delete this.queryParams.dateHigh
}
this.initList()
},
onClearStartDate() {
this.startDate = null
this.dateLow = null
delete this.queryParams.startDate
delete this.queryParams.dateLow
this.initList()
},
@ -564,28 +456,27 @@
onEndDateChange(e) {
const date = e.value
this.queryParams.endDate = date
this.queryParams.dateHigh = $dayjs(date).format('YYYY-MM-DD')
const { startDate } = this.queryParams
const { dateLow } = this.queryParams
if (startDate && this.$dayjs(date).isBefore(startDate, 'day')) {
this.startDate = null
delete this.queryParams.startDate
if (dateLow && this.$dayjs(date).isBefore(dateLow, 'day')) {
this.dateLow = null
delete this.queryParams.dateLow
}
this.initList()
},
onClearEndDate() {
this.endDate = null
this.dateHigh = null
delete this.queryParams.endDate
delete this.queryParams.dateHigh
this.initList()
},
onSortChange(sort) {
console.log('onSortChange', sort)
// todo set sort
this.getData()
this.initList()
},
}
}


+ 26
- 10
pages/index/growing.vue View File

@ -21,7 +21,7 @@
</view>
<view class="archives">
<userCard @switchMember="jumpToChooseMember" @addRecord="onAdd"></userCard>
<userCard :medalList="medalList" :medalCount="medalCount" :experienceCount="total" @switchMember="jumpToChooseMember" @addRecord="onAdd"></userCard>
</view>
<view class="list" v-if="memberInfo">
@ -31,7 +31,7 @@
<button class="btn btn-choose" @click="jumpToChooseMember">请先选择人员</button>
</view>
<record-form-popup ref="recordFormPopup" @submitted="getData"></record-form-popup>
<!-- <record-form-popup ref="recordFormPopup" @submitted="getData"></record-form-popup> -->
<tabber select="growing" />
@ -46,14 +46,14 @@
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/comment/recordFormPopup.vue'
// import recordFormPopup from '@/pages_order/comment/recordFormPopup.vue'
export default {
mixins: [mixinsList],
components: {
userCard,
recordsView,
recordFormPopup,
// recordFormPopup,
tabber,
},
data() {
@ -63,8 +63,10 @@
queryParams: {
pageNo: 1,
pageSize: 10,
userId: '',
// userId: '',
},
medalList: [],
medalCount: 0,
}
},
computed: {
@ -87,16 +89,19 @@
}
// this.queryParams.userId = this.memberInfo.id
// this.getData()
if(uni.getStorageSync('token')){
this.fetchMedalList()
}
},
methods: {
getDataThen(records) {
this.list = records.map(item => {
const { id, name, image, createTime } = item
const { id, activityTitle, activityId_dictText, image, createTime } = item
return {
id,
// todo: check key
name,
name: activityTitle || activityId_dictText || '',
image: image?.split?.(',') || [],
createTime
}
@ -109,9 +114,20 @@
})
// this.keyword = ''
},
onAdd() {
this.$refs.recordFormPopup.open()
async fetchMedalList() {
try {
const result = await this.$fetch('queryMedalList', { pageNo: 1, pageSize: 1000, isLight: '1', })
const { records, total } = result
this.medalList = records
this.medalCount = total
} catch (err) {
}
},
// onAdd() {
// this.$refs.recordFormPopup.open()
// },
jumpToChooseMember() {
uni.navigateTo({
url: `/pages_order/member/switch`


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

@ -50,7 +50,7 @@
</view>
<view class="section">
<productView :list="list"></productView>
<productView :list="list" @collect="getData"></productView>
</view>
</view>
@ -102,10 +102,26 @@
showBtnService: true,
}
},
onLoad() {
onLoad(arg) {
const { shareId } = arg
if (shareId) {
uni.setStorageSync('shareId', shareId)
}
if(uni.getStorageSync('token')){
this.$store.commit('getUserInfo')
}
if (shareId && !uni.getStorageSync('token')) {
uni.navigateTo({
url: '/pages_order/auth/wxLogin'
})
return
}
},
computed: {
searchStyle() {


+ 28
- 106
pages/index/partner.vue View File

@ -18,22 +18,22 @@
<view class="card">
<view class="flex user">
<view class="avatar">
<image class="img" src="@/pages_order/static/temp-30.png" mode="scaleToFill"></image>
<view :class="['tag', `tag-1`]">家长</view>
<image class="img" :src="userInfo.headImage" mode="scaleToFill"></image>
<view :class="['tag', `tag-${userInfo.role}`]">{{ userInfo.roleDesc || '' }}</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 class="summary-item-content text-ellipsis">{{ userInfo.nickName }}</view>
<view class="summary-item-label text-ellipsis">{{ `ID:${userInfo.id}` }}</view>
</view>
<template v-if="isPartner">
<view class="flex flex-column summary-item" @click="jumpToAchievement">
<view class="summary-item-content">888</view>
<view class="summary-item-label">推广人数</view>
<view class="summary-item-content">{{ userCenterData.spreadNum }}</view>
<view class="summary-item-label nowrap">推广人数</view>
</view>
<view class="flex flex-column summary-item">
<view class="summary-item-content">341</view>
<view class="summary-item-label">总佣金</view>
<view class="summary-item-content">{{ userCenterData.commission }}</view>
<view class="summary-item-label nowrap">总佣金</view>
</view>
</template>
<template v-else>
@ -74,12 +74,12 @@
<view class="flex list-item" v-for="item in list" :key="item.id">
<view class="flex col info">
<view class="avatar">
<image class="img" :src="item.avatar" mode="scaleToFill"></image>
<image class="img" :src="item.commissionUser.headImage" mode="scaleToFill"></image>
</view>
<view>{{ item.name }}</view>
<view>{{ item.commissionUser.nickName }}</view>
</view>
<view class="col price">{{ `${item.price}` }}</view>
<view class="col desc">{{ item.createTime }}</view>
<view class="col price">{{ `${item.amount}` }}</view>
<view class="col desc">{{ $dayjs(item.createTime).format('YYYY-MM-DD') }}</view>
</view>
</view>
</view>
@ -92,10 +92,12 @@
</template>
<script>
import { mapState } from 'vuex'
import mixinsList from '@/mixins/list.js'
import tabber from '@/components/base/tabbar.vue'
import posterPopup from '@/components/partner/posterPopup.vue'
import posterPopup from '@/components/base/posterPopup.vue'
export default {
mixins: [mixinsList],
@ -108,116 +110,33 @@
advantages: ['收益高', '品类全', '到账快', '城市多'],
// todo: fetch
isPartner: true,
// todo
mixinsListApi: '',
mixinsListApi: 'queryCommissionList',
}
},
computed: {
...mapState(['userCenterData']),
},
onLoad() {
if(uni.getStorageSync('token')){
this.$store.commit('getUserInfo')
this.$store.commit('getUserCenterData')
}
},
onShow() {
// todo: refresh is partner?
},
methods: {
// todo: delete
getData() {
this.list = [
{
id: '001',
avatar: '/pages_order/static/temp-30.png',
name: '李世海',
price: 10,
createTime: '2025-07-15',
},
{
id: '002',
avatar: '/pages_order/static/temp-30.png',
name: '周静',
price: 10,
createTime: '2025-07-15',
},
{
id: '003',
avatar: '/pages_order/static/temp-30.png',
name: '周海',
price: 10,
createTime: '2025-07-15',
},
{
id: '004',
avatar: '/pages_order/static/temp-30.png',
name: '冯启彬',
price: 10,
createTime: '2025-07-15',
},
{
id: '005',
avatar: '/pages_order/static/temp-30.png',
name: '李娉',
price: 10,
createTime: '2025-07-15',
},
{
id: '006',
avatar: '/pages_order/static/temp-30.png',
name: '李书萍',
price: 10,
createTime: '2025-07-15',
},
{
id: '007',
avatar: '/pages_order/static/temp-30.png',
name: '李世海',
price: 10,
createTime: '2025-07-15',
},
{
id: '008',
avatar: '/pages_order/static/temp-30.png',
name: '周静',
price: 10,
createTime: '2025-07-15',
},
{
id: '009',
avatar: '/pages_order/static/temp-30.png',
name: '周海',
price: 10,
createTime: '2025-07-15',
},
{
id: '010',
avatar: '/pages_order/static/temp-30.png',
name: '冯启彬',
price: 10,
createTime: '2025-07-15',
},
{
id: '011',
avatar: '/pages_order/static/temp-30.png',
name: '李娉',
price: 10,
createTime: '2025-07-15',
},
{
id: '012',
avatar: '/pages_order/static/temp-30.png',
name: '李书萍',
price: 10,
createTime: '2025-07-15',
},
]
},
onApplyPartner() {
// todo: check
return
this.$utils.navigateTo(`/pages_order/partner/apply`)
},
jumpToTeam() {
this.$utils.navigateTo(`/pages_order/partner/team`)
},
openPosterPopup() {
this.$refs.posterPopup.open()
const path = `pages/index/index?shareId=${this.userInfo.id}`
this.$refs.posterPopup.open(path)
},
jumpToWithdraw() {
this.$utils.navigateTo(`/pages_order/partner/withdraw`)
@ -331,7 +250,8 @@
row-gap: 8rpx;
&.name {
flex: none;
min-width: 0;
// flex: none;
align-items: flex-start;
}
@ -340,12 +260,14 @@
}
&-content {
width: 100%;
font-size: 32rpx;
font-weight: 600;
color: #000000;
}
&-label {
width: 100%;
font-size: 24rpx;
color: #939393;
}


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

@ -91,6 +91,10 @@
api = 'queryPolicyById',
idKey = 'policyId'
break
case 'queryJournalList':
api = 'queryJournalById',
idKey = 'journalId'
break
default:
break
}


+ 141
- 0
pages_order/auth/roleChoose.vue View File

@ -0,0 +1,141 @@
<template>
<view class="page__view">
<navbar title="选择角色" leftClick @leftClick="$utils.navigateBack" bgColor="transparent" color="#191919" />
<view class="main">
<view class="card">
<view class="card-header">角色信息</view>
<view class="card-content">
<uv-radio-group v-model="role" placement="column" activeColor="#00A9FF">
<view class="flex option" v-for="item in roleOptions" :key="item.id" @click="onSelect(item.value)">
<view>
<uv-radio :name="item.value"></uv-radio>
</view>
<view>{{ item.label }}</view>
</view>
</uv-radio-group>
</view>
</view>
</view>
<view class="flex bottom">
<view class="flex bar">
<button class="btn" @click="onConfirm">确定</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
role: null,
roleOptions: [],
}
},
onLoad() {
this.roleOptions = [
{ id: '001', label: '家长', value: '0' },
{ id: '002', label: '学生', value: '1' },
]
this.role = this.roleOptions[0].value
},
methods: {
onSelect(val) {
this.role = val
},
async onConfirm() {
try {
await this.$fetch('updateInfo', params, false)
uni.showToast({
icon: 'success',
title: '保存成功',
});
this.$store.commit('getUserInfo')
setTimeout(() => {
uni.reLaunch({
url:'/pages/index/index'
})
}, 800)
} catch (err) {
}
},
},
}
</script>
<style scoped lang="scss">
.page__view {
background-image: linear-gradient(#DAF3FF, #F3F3F3 200rpx, #F3F3F3);
}
.main {
padding: 24rpx 40rpx;
}
.card {
width: 100%;
height: 100%;
padding: 32rpx 32rpx 450rpx 32rpx;
box-sizing: border-box;
background: #FFFFFF;
border-radius: 24rpx;
&-header {
font-size: 32rpx;
font-weight: 500;
color: #181818;
}
}
.option {
margin-top: 32rpx;
width: 100%;
height: 252rpx;
justify-content: flex-start;
column-gap: 24rpx;
padding: 24rpx;
box-sizing: border-box;
background: #F5F8FF;
border-radius: 24rpx;
&:nth-child(2n) {
background: #F9F9F9;
}
}
.bottom {
position: fixed;
left: 0;
bottom: 0;
width: 100vw;
height: 146rpx;
padding-bottom: env(safe-area-inset-bottom);
background: #FFFFFF;
.bar {
width: 100%;
padding: 0 40rpx;
box-sizing: border-box;
}
.btn {
width: 100%;
padding: 16rpx 0;
box-sizing: border-box;
font-size: 36rpx;
color: #FFFFFF;
background-image: linear-gradient(to right, #21FEEC, #019AF9);
border: 2rpx solid $uni-color;
border-radius: 42rpx;
}
}
</style>

+ 1
- 2
pages_order/auth/wxUserInfo.vue View File

@ -176,8 +176,6 @@
title: '保存成功',
});
this.$store.commit('getUserInfo')
setTimeout(() => {
if (this.mode === 'edit') {
this.$utils.navigateBack()
@ -186,6 +184,7 @@
uni.reLaunch({
url:'/pages/index/index'
})
this.$store.commit('getUserInfo')
}, 800)
}


+ 42
- 29
pages_order/comment/recordFormPopup.vue View File

@ -13,14 +13,14 @@
errorType="toast"
>
<view class="form-item">
<uv-form-item prop="activityId" :customStyle="formItemStyle">
<uv-form-item prop="experienceId" :customStyle="formItemStyle">
<view class="form-item-label">
<image class="icon" src="@/pages_order/static/icon-require.png" mode="widthFix"></image>
关联项目
</view>
<view class="form-item-content">
<view class="flex row" @click="openRelatePojectPicker">
<view v-if="form.activityId" class="text">{{ projectDesc }}</view>
<view v-if="form.experienceId" class="text">{{ projectDesc }}</view>
<view v-else class="text placeholder">请选择关联项目</view>
<uv-icon name="arrow-right" color="#C6C6C6" size="32rpx"></uv-icon>
</view>
@ -28,7 +28,7 @@
</view>
</uv-form-item>
</view>
<view class="form-item">
<!-- <view class="form-item">
<uv-form-item prop="processScore" :customStyle="formItemStyle">
<view class="flex row">
<view class="form-item-label">行程</view>
@ -57,7 +57,7 @@
</view>
</view>
</uv-form-item>
</view>
</view> -->
<view class="form-item">
<uv-form-item prop="images" :customStyle="formItemStyle">
<view class="form-item-label">上传图片</view>
@ -106,10 +106,10 @@
return {
isShow: false,
form: {
activityId: null,
processScore: null,
spotScore: null,
teacherScore: null,
experienceId: null,
// processScore: null,
// spotScore: null,
// teacherScore: null,
images: [],
texts: [],
},
@ -119,8 +119,8 @@
computed: {
...mapState(['userInfo', 'configList']),
projectDesc() {
const { activityId } = this.form
const target = this.projects?.find?.(item => item.id === activityId)
const { experienceId } = this.form
const target = this.projects?.find?.(item => item.id === experienceId)
return target?.name || ''
},
@ -132,7 +132,7 @@
this.projects = records.map(item => {
return {
id: item.activityId,
id: item.id,
name: item.activityTitle || item.activityId_dictText || ''
}
})
@ -142,19 +142,32 @@
},
setRules() {
const rules = {
'activityId': {
'experienceId': {
type: 'string',
required: true,
message: '请选择关联项目',
},
'texts': {
type: 'array',
required: true,
message: '请完整回答',
validator: (rule, value, callback) => {
if (value.every(val => !!val)) {
return true
}
return false
},
},
}
// todo: check
// this.questions.forEach((item, index) => {
// this.configList.experienceQuestionList.forEach((item, index) => {
// rules[`texts[${index}]`] = {
// type: 'string',
// required: true,
// message: `${item.label}`,
// message: `${item.question}`,
// }
// })
@ -170,10 +183,10 @@
const texts = this.configList.experienceQuestionList.map(() => '')
this.form = {
activityId: null,
processScore: null,
spotScore: null,
teacherScore: null,
experienceId: null,
// processScore: null,
// spotScore: null,
// teacherScore: null,
images: [],
texts,
}
@ -192,20 +205,20 @@
}, 800)
},
openRelatePojectPicker() {
this.$refs.reloateProjectPopup.open(this.form.activityId || null)
this.$refs.reloateProjectPopup.open(this.form.experienceId || null)
},
onRelateProjectChange(id) {
this.form.activityId = id
this.form.experienceId = id
},
async onPublish() {
try {
await this.$refs.form.validate()
const {
activityId,
processScore,
spotScore,
teacherScore,
experienceId,
// processScore,
// spotScore,
// teacherScore,
images,
texts,
} = this.form
@ -213,12 +226,12 @@
const params = {
// todo: check
userId: this.userInfo.id,
activityId,
processScore,
spotScore,
teacherScore,
experienceId,
// processScore,
// spotScore,
// teacherScore,
image: images.join(','),
content: texts.join('\r\n') // todo: check
content: texts.join('\r\n')
}
await this.$fetch('addExperience', params)


+ 21
- 77
pages_order/growing/achievement/index.vue View File

@ -8,7 +8,7 @@
<view class="flex summary">
<view class="info">
<view class="flex title">共获得<view class="highlight">8</view>枚成就</view>
<view class="flex title">共获得<view class="highlight">{{ total }}</view>枚成就</view>
<view class="tag">新获得</view>
</view>
<view class="icon">
@ -17,7 +17,7 @@
</view>
<view class="list">
<recordsView :list="list"></recordsView>
<recordsView :list="list" @lighted="getData"></recordsView>
</view>
</view>
@ -37,8 +37,7 @@
data() {
return {
keyword: '',
// todo
mixinsListApi: '',
mixinsListApi: 'queryMedalList',
}
},
onLoad({ search }) {
@ -50,82 +49,27 @@
this.getData()
},
methods: {
// todo: delete
getData() {
this.list = [
{
id: '001',
name: '呼伦贝尔6/8日丨经典or环线 双套餐可选',
children: [
{
id: '0011',
icon: '/pages_order/static/temp-47.png',
label: '言值认证',
createTime: '2025-07-12',
},
{
id: '0012',
icon: '/pages_order/static/temp-47.png',
label: '国际旅行',
createTime: '2025-07-12',
},
{
id: '0013',
icon: '/pages_order/static/temp-47.png',
label: '萌新毕业证',
createTime: '2025-07-12',
},
],
},
{
id: '002',
name: '新疆天山行7/9日丨醉美伊犁&吐鲁番双套餐新疆天山行7/9日丨醉美伊犁&吐鲁番双套餐',
children: [
{
id: '0011',
icon: '/pages_order/static/temp-47.png',
label: '言值认证',
createTime: '2025-05-15',
},
],
},
{
id: '003',
name: '呼伦贝尔6/8日丨经典or环线 双套餐可选',
children: [
{
id: '0011',
icon: '/pages_order/static/temp-47.png',
label: '言值认证',
createTime: '2025-07-12',
},
{
id: '0012',
icon: '/pages_order/static/temp-47.png',
label: '国际旅行',
createTime: '2025-07-12',
},
],
},
{
id: '004',
name: '新丝路到敦煌7日丨甘青轻松穿越,沙漠+草原',
getDataThen(records) {
this.list = records.map(item => {
const { id, activityId_dictText, medalId, medal, createTime, isLight: _isLight } = item
const { title, icon1, icon2 } = medal
const isLight = _isLight == '1'
return {
id,
name: activityId_dictText,
children: [
{
id: '0012',
icon: '/pages_order/static/temp-47.png',
label: '国际旅行',
createTime: '2025-07-12',
},
{
id: '0013',
icon: '/pages_order/static/temp-47.png',
label: '萌新毕业证',
createTime: '2025-07-12',
medalId,
icon: isLight ? icon1 : icon2,
label: title,
createTime,
isLight,
},
],
},
]
]
}
})
},
},
}


+ 12
- 2
pages_order/growing/achievement/recordsView.vue View File

@ -16,10 +16,10 @@
<template #desc>
<view class="content">
<view class="list">
<view class="flex flex-column list-item" v-for="(child, cIdx) in record.children" :key="cIdx">
<view class="flex flex-column list-item" v-for="(child, cIdx) in record.children" :key="cIdx" @click="onLightMedal(child.medalId)">
<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 class="list-item-desc">{{ $dayjs(child.createTime).format('YYYY-MM-DD') }}</view>
</view>
</view>
</view>
@ -39,6 +39,16 @@
}
}
},
methods: {
async onLightMedal(medalId) {
try {
await this.$fetch('lightMedal', { medalId })
this.$emit('lighted')
} catch (err) {
}
},
},
}
</script>


+ 210
- 0
pages_order/growing/activity/commentCard.vue View File

@ -0,0 +1,210 @@
<template>
<view class="card">
<view class="flex header">
<view class="flex left">
<view class="avatar">
<image class="avatar-img" :src="data.user.headImage" mode="scaleToFill"></image>
</view>
<view class="info">
<view class="name">{{ data.user.nickName }}</view>
<view>{{ $dayjs(data.createTime).format('YYYY-MM-DD') }}</view>
</view>
</view>
</view>
<view class="section content">{{ data.content }}</view>
<view class="flex section imgs">
<image class="img"
v-for="(url, iIdx) in images"
:key="iIdx" :src="url"
mode="scaleToFill"
></image>
</view>
</view>
</template>
<script>
import formRate from '@/pages_order/components/formRate.vue'
export default {
components: {
formRate,
},
props: {
data: {
type: Object,
default() {
return {}
}
},
mode: {
type: String,
default: 'read' // read | edit
}
},
computed: {
images() {
const { image } = this.data || {}
return image?.split?.(',')?.filter(val => val)
},
disabled() {
return this.mode !== 'edit'
},
},
methods: {
},
}
</script>
<style scoped lang="scss">
.card {
width: 100%;
padding: 32rpx;
box-sizing: border-box;
background: #FAFAFF;
border: 2rpx solid #FFFFFF;
border-radius: 32rpx;
}
.header {
.left {
flex: 1;
justify-content: flex-start;
column-gap: 24rpx;
}
.avatar {
width: 100rpx;
height: 100rpx;
border: 4rpx solid #FFFFFF;
border-radius: 50%;
overflow: hidden;
&-img {
width: 100%;
height: 100%;
}
}
.info {
font-family: PingFang SC;
font-weight: 400;
font-size: 24rpx;
line-height: 1.5;
color: #8B8B8B;
.name {
font-weight: 600;
font-size: 36rpx;
line-height: 1.2;
color: #252545;
margin-bottom: 8rpx;
}
}
.btn {
&-icon {
width: 44rpx;
height: auto;
}
}
}
.section {
margin-top: 24rpx;
}
.content {
white-space: pre-wrap;
font-family: PingFang SC;
font-weight: 400;
font-size: 32rpx;
line-height: 1.4;
color: #181818;
}
.imgs {
justify-content: flex-start;
flex-wrap: wrap;
gap: 24rpx;
.img {
width: 190rpx;
height: 190rpx;
}
}
.score {
&-item {
padding: 12rpx 0;
justify-content: space-between;
& + & {
margin-top: 4rpx;
}
&-label {
font-family: PingFang SC;
font-weight: 400;
font-size: 26rpx;
line-height: 1.4;
color: #181818;
}
}
}
.operate {
justify-content: flex-start;
column-gap: 24rpx;
.btn {
column-gap: 8rpx;
font-size: 24rpx;
color: #999999;
.icon {
width: 32rpx;
height: auto;
}
}
}
.comment {
&-user {
column-gap: 16rpx;
.avatar {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
&-img {
width: 100%;
height: 100%;
}
}
.name {
font-size: 26rpx;
font-weight: 600;
color: #000000;
}
.time {
flex: 1;
text-align: right;
font-size: 24rpx;
color: #999999;
}
}
&-content {
margin-top: 28rpx;
font-size: 26rpx;
color: #666666;
}
}
</style>

+ 50
- 30
pages_order/growing/activity/index.vue View File

@ -44,17 +44,20 @@
</view>
<view>活动掠影</view>
</view>
<view class="btn btn-mark" @click="onMark">标记有我</view>
<view class="btn" @click="onMark">标记有我</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>
<image class="img" :src="image" mode="scaleToFill" @click="previewImage(detail.highlights, idx)"></image>
</view>
</view>
</view>
<view class="section" id="thoughts">
<view class="section-header">学员心得</view>
<view class="flex section-header">
<view>学员心得</view>
<view class="btn" @click="onAdd">新增心得</view>
</view>
<view class="section-content thoughts">
<view class="card" v-for="item in detail.thoughts" :key="item.id">
<commentCard :data="item" @change="onCommentChange"></commentCard>
@ -75,21 +78,30 @@
<markPopup ref="markPopup"></markPopup>
<record-form-popup ref="recordFormPopup" @submitted="getData"></record-form-popup>
<posterPopup ref="posterPopup"></posterPopup>
<view class="flex bottom">
<button class="flex btn btn-palin" @click="jumpToPoster">生成海报</button>
<button class="flex btn btn-palin" @click="openPosterPopup">生成海报</button>
<button class="flex btn btn-primary" @click="onApplyEmail">申请邮件</button>
</view>
</view>
</template>
<script>
import commentCard from '@/pages_order/comment/commentCard.vue'
// import commentCard from '@/pages_order/comment/commentCard.vue'
import commentCard from './commentCard.vue'
import markPopup from '@/pages_order/growing/activity/markPopup.vue'
import recordFormPopup from '@/pages_order/comment/recordFormPopup.vue'
import posterPopup from '@/components/base/posterPopup.vue'
export default {
components: {
commentCard,
markPopup,
recordFormPopup,
posterPopup,
},
data() {
return {
@ -105,7 +117,21 @@
}
},
onLoad(arg) {
const { id } = arg
const { id, shareId } = arg
if (shareId) {
uni.setStorageSync('shareId', shareId)
}
if (shareId && !uni.getStorageSync('token')) {
uni.navigateTo({
url: '/pages_order/auth/wxLogin'
})
return
}
this.id = id
this.getData()
@ -115,35 +141,19 @@
try {
const result = await this.$fetch('queryExperienceById', { recordId: this.id })
const { image, thoughts } = result
const { imageContentList, experienceList } = result
this.detail = {
highlights: image?.split?.(',') || [],
// todo: check key
thoughts,
highlights: imageContentList.reduce((arr, item) => {
return arr.concat(item.image?.split?.(',') || [])
}, []),
thoughts: experienceList,
// todo: check key about ""
}
} catch (err) {
}
return
// todo
this.detail = {
highlights: [
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
],
thoughts: [],
}
},
//tab
clickTabs({ index }) {
@ -153,8 +163,9 @@
onCommentChange() {
// todo: refresh comment list
},
jumpToPoster() {
// todo
openPosterPopup() {
const path = `/pages_order/growing/activity/index?id=${this.id}&shareId=${this.userInfo.id}`
this.$refs.posterPopup.open(path)
},
onApplyEmail() {
this.$utils.navigateTo(`/pages_order/growing/activity/applyEmail?id=${this.id}`)
@ -162,6 +173,15 @@
onMark() {
this.$refs.markPopup.open(this.id)
},
onAdd() {
this.$refs.recordFormPopup.open()
},
previewImage(arr, index) {
uni.previewImage({
urls: arr,
current: arr[index], //
});
},
},
}
</script>
@ -217,7 +237,7 @@
}
}
.btn-mark {
.btn {
padding: 6rpx 22rpx;
font-family: PingFang SC;
font-size: 28rpx;


+ 42
- 42
pages_order/growing/activity/search.vue View File

@ -24,7 +24,7 @@
</view>
<view class="main">
<sortBar v-model="queryParams.sort" @change="onSortChange"></sortBar>
<sortBar v-model="sort" @change="onSortChange"></sortBar>
<view v-if="list.length" class="list">
<recordsView :list="list"></recordsView>
@ -53,68 +53,68 @@
return {
keyword: '',
isFocusSearch: false,
sort: 'comprehensive',
queryParams: {
pageNo: 1,
pageSize: 10,
title: '',
sort: 'comprehensive',
activityTitle: '',
// sort: 'comprehensive',
},
// todo
mixinsListApi: '',
mixinsListApi: 'queryExperienceList',
}
},
onLoad({ search }) {
if (search) {
this.keyword = search
this.queryParams.title = search
this.queryParams.activityTitle = search
}
this.getData()
},
methods: {
// todo: delete
getData() {
this.list = [
{
id: '001',
name: '新疆天山行7/9日丨醉美伊犁&吐鲁番双套餐',
image: [
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
],
createTime: '2025-07-12',
},
{
id: '002',
name: '仙踪新昌·韩妃江古道|邂逅“江南小桂林”',
image: [
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
],
createTime: '2025-06-18',
},
{
id: '003',
name: '山水石窟·大佛寺|江南佛窟造像,新昌山水轻徒',
image: [
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
'/pages_order/static/temp-38.png',
],
createTime: '2025-06-15',
},
]
},
search() {
this.queryParams.pageNo = 1
this.queryParams.pageSize = 10
this.queryParams.title = this.keyword
if (this.keyword) {
this.queryParams.activityTitle = this.keyword
} else {
delete this.queryParams.activityTitle
}
this.getData()
},
onSortChange(sort) {
console.log('onSortChange', sort)
this.getda()
},
beforeGetData() {
console.log('beforeGetData')
let params = {}
switch(this.sort) {
// (dateOrder)0- 1-
case 'date-desc': // -
params.dateOrder = '0'
break
case 'date-asc': // -
params.dateOrder = '1'
break
default:
break
}
return params
},
getDataThen(records) {
this.list = records.map(item => {
const { id, activityTitle, activityId_dictText, image, createTime } = item
return {
id,
name: activityTitle || activityId_dictText || '',
image: image?.split?.(',') || [],
createTime
}
})
},
},
}


+ 7
- 7
pages_order/growing/activity/sortBar.vue View File

@ -9,11 +9,11 @@
<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 :class="['flex', 'sort-item', ['date-asc', 'date-desc'].includes(sort) ? 'is-active' : '']" @click="onClickSort('date')">
<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>
<uv-icon v-if="sort == 'date-asc'" name="arrow-up-fill" color="#00A9FF" size="16rpx" :bold="true"></uv-icon>
<uv-icon v-else-if="sort == 'date-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>
@ -58,11 +58,11 @@
sort = 'sale-desc'
}
break;
case 'price':
if (this.sort == 'price-desc') {
sort = 'price-asc'
case 'date':
if (this.sort == 'date-desc') {
sort = 'date-asc'
} else {
sort = 'price-desc'
sort = 'date-desc'
}
break;
default:


+ 4
- 50
pages_order/order/components/orderInfoView.vue View File

@ -7,16 +7,16 @@
{{ statusDesc }}
</view>
</view>
<view class="flex member" v-for="item in members" :key="item.id">
<view class="flex member" v-for="item in data.orderPersonList" :key="item.id">
<view class="info">
<view class="flex info-top">
<view>{{ item.name }}</view>
<view>{{ getTypeDesc(item.type) }}</view>
<view class="light">{{ getTypeTips(item.type) }}</view>
<view>{{ item.period || '' }}</view>
<view class="light">{{ item.periodDesc || '' }}</view>
</view>
<view class="info-bottom">{{ item.cerNo }}</view>
</view>
<view class="flex price">¥<view class="highlight">{{ item.price }}</view></view>
<view class="flex price">¥<view class="highlight">{{ item.price || '-' }}</view></view>
</view>
<view class="row">
<view class="row-label">总价格</view>
@ -63,16 +63,6 @@
1: '已支付',
2: '已完成',
}
const MEMBER_TYPE_AND_DESC_MAPPING = {
0: '成人',
1: '青少年',
2: '儿童',
}
const MEMBER_TYPE_AND_TIPS_MAPPING = {
0: '(18周岁以上)',
1: '(14周岁以上)',
2: '(14周岁以下)',
}
export default {
props: {
@ -88,44 +78,8 @@
const { status } = this.data
return ORDER_STATUS_AND_DESC_MAPPING[status]
},
productPackage() {
const { time, product } = this.data
const { timeOptions } = product || {}
return timeOptions?.find?.(item => item.id === time) || {}
},
members() {
const { members } = this.data
const { adultsPrice, teenagerPrice, childPrice } = this.productPackage
return members?.map?.(item => {
const { type } = item
let price = 0
switch(type) {
case 0: //
price = adultsPrice
break
case 1: //
price = teenagerPrice
break
case 2: //
price = childPrice
break
}
return { ...item, price }
})
},
},
methods: {
getTypeDesc(type) {
return MEMBER_TYPE_AND_DESC_MAPPING[type]
},
getTypeTips(type) {
return MEMBER_TYPE_AND_TIPS_MAPPING[type]
},
},
}
</script>


+ 26
- 1
pages_order/order/orderConfirm/index.vue View File

@ -246,9 +246,15 @@
const {
time,
prices,
priceList,
members,
} = this.orderInfo
console.log('priceList', priceList)
console.log('members', members)
let arr = []
this.form = {
name: '',
phone: '',
@ -294,8 +300,12 @@
const {
product,
time,
priceList,
} = this.orderInfo
console.log('priceList', priceList)
console.log('members', members)
const {
name,
phone,
@ -306,6 +316,20 @@
} = this.form
const { startDate, endDate } = this.productPackage
// let json = members.map(member => {
// const { periodId } = member
// const { period_dictText, price } = priceList.find(item => item.periodId === periodId) || {}
// return {
// ...member,
// period_dictText,
// price,
// }
// })
// console.log('json', json)
let params = {
activityId: product.id,
startDate,
@ -319,7 +343,8 @@
discount: this.discount,
priceDiscount: this.totalPrice,
payAmount: this.totalPrice,
tourisIds: members.map(touris => touris.id).join(',')
tourisIds: members.map(touris => touris.id).join(';'),
// jsonObject: JSON.stringify(json),
}
const orderId = await this.$fetch('createOrder', params)


+ 5
- 9
pages_order/order/orderConfirm/peopleNumberInput.vue View File

@ -3,8 +3,7 @@
<view class="flex row" v-for="(item, index) in options" :key="item.id">
<view class="flex row-label">
<view class="title">{{ item.period_dictText }}</view>
<!-- todo: check key -->
<view class="desc" v-if="getTypeDesc(item.period_dictText)">{{ `(${getTypeDesc(item.period_dictText)})` }}</view>
<view class="desc" v-if="getTypeDesc(item.periodId)">{{ `(${getTypeDesc(item.periodId)})` }}</view>
<view class="flex price">
<text>¥</text>
<text class="highlight">{{ item.price }}</text>
@ -32,11 +31,6 @@
</template>
<script>
const TYPE_DESC = {
'成人': '(18周岁以上)',
'青少年': '(14周岁以上)',
'儿童': '(14周岁以下)',
}
export default {
props: {
@ -73,8 +67,10 @@
prices[index] = value
this.prices = prices
},
getTypeDesc(type) {
return TYPE_DESC[type]
getTypeDesc(periodId) {
const remark = this.configList.periodList.find(item => item.id === periodId)?.remark
return remark ? `(${remark})` : ''
},
},
}


+ 2
- 2
pages_order/partner/team.vue View File

@ -34,9 +34,9 @@
<view class="list">
<view class="flex list-item" v-for="item in list" :key="item.id">
<view class="avatar">
<image class="img" :src="item.avatar" mode="scaleToFill"></image>
<image class="img" :src="item.headImage" mode="scaleToFill"></image>
</view>
<view>{{ item.name }}</view>
<view>{{ item.nickName }}</view>
</view>
</view>


+ 1
- 1
pages_order/product/commentList.vue View File

@ -6,7 +6,7 @@
<image class="img" :src="item.user.headImage" mode="scaleToFill"></image>
</view>
<view class="name">{{ item.user.nickName }}</view>
<view class="time">{{ $dayjs(item.createTime).format('YYYY-MM-DD') }}</view>
<view class="time nowrap">{{ $dayjs(item.createTime).format('YYYY-MM-DD') }}</view>
</view>
<view class="flex content">
<view class="content-text">{{ item.content }}</view>


+ 8
- 8
pages_order/product/productDetail.vue View File

@ -100,7 +100,7 @@
<view>客服</view>
</button>
<view class="flex operate">
<button class="flex btn btn-palin" @click="onCollect">收藏</button>
<button class="flex btn btn-palin" @click="onCollect">{{ isCollected ? '移除收藏' : '收藏' }}</button>
<button class="flex btn btn-primary" @click="onBuy">立即购买</button>
</view>
</view>
@ -175,7 +175,10 @@
}
return ''
}
},
isCollected() {
return this.detail.isCollection == '1'
},
},
onLoad(arg) {
const { id } = arg
@ -191,7 +194,6 @@
try {
const result = await this.$fetch('queryActivityById', { activityId })
// todo: init options
this.detail = result
} catch (err) {
@ -206,16 +208,14 @@
activityId,
}
this.commentList = (await this.$fetch('queryCommentList', queryParams))?.records
console.log('commentList', this.commentList)
} catch (err) {
console.log('fetchComment', err)
}
},
onCollect() {
this.$store.dispatch('collect', this.id)
// todo: set collectd and change btn to cancel collect?
async onCollect() {
let succ = await this.$store.dispatch('collect', this.id)
succ && this.fetchDetail(this.id)
},
onBuy() {
this.$refs.orderInfoPopup.open({ selectTime: this.selectTime })


+ 35
- 6
store/store.js View File

@ -61,10 +61,16 @@ const store = new Vuex.Store({
if(res.errMsg != "login:ok"){
return
}
let data = {
code: res.code,
}
if (uni.getStorageSync('shareId')) {
data.vid = uni.getStorageSync('shareId')
}
api('wxLogin', {
code : res.code
}, res => {
api('wxLogin', data, res => {
uni.hideLoading()
@ -90,7 +96,26 @@ const store = new Vuex.Store({
getUserInfo(state){
api('getInfo', res => {
if(res.code == 200){
state.userInfo = res.result
const result = res.result
switch (result.role) {
case '0':
result.roleDesc = '家长'
break
case '1':
result.roleDesc = '学生'
break
default:
break
}
state.userInfo = result
if (!state.userInfo.role) {
uni.navigateTo({
url: '/pages_order/auth/roleChoose'
})
}
}
})
},
@ -141,16 +166,20 @@ const store = new Vuex.Store({
try {
await fetch('collectionActivity', { activityId })
const res = await fetch('collectionActivity', { activityId }, false)
uni.showToast({
icon: 'success',
title: '已收藏',
title: res.message,
});
return true
} catch (err) {
console.log('collect err', err)
uni.showToast({
icon: 'error',
title: err.message,
});
return false
}
},


Loading…
Cancel
Save