Browse Source

feat: 新增任务中心、订单详情、个人信息页面及功能

- 新增任务中心页面,支持任务列表展示、任务接受、任务提交等功能
- 新增订单详情页面,展示订单详细信息及服务记录
- 新增个人信息页面,支持用户头像、昵称等信息修改
- 新增配置信息初始化功能,优化全局配置管理
- 新增新用户优惠券弹窗,提升用户体验
master
前端-胡立永 6 months ago
parent
commit
c68077f701
18 changed files with 2282 additions and 544 deletions
  1. +1
    -0
      App.vue
  2. +12
    -0
      api/system/configList.js
  3. +39
    -0
      api/system/task.js
  4. +5
    -0
      main.js
  5. +61
    -0
      mixins/configList.js
  6. +78
    -0
      mixins/list.js
  7. +36
    -0
      pages.json
  8. +120
    -0
      pages/components/NewUserCoupon.vue
  9. +32
    -1
      pages/index.vue
  10. +466
    -447
      pages/newOrder/petList.vue
  11. +39
    -5
      pages/personalCenter/index.vue
  12. +332
    -0
      pages/personalCenter/orderDetail.vue
  13. +367
    -0
      pages/personalCenter/taskDetail.vue
  14. +298
    -0
      pages/personalCenter/taskList.vue
  15. +261
    -0
      pages/personalCenter/userInfo.vue
  16. +28
    -5
      store/index.js
  17. +103
    -85
      store/modules/user.js
  18. +4
    -1
      utils/getUrl.js

+ 1
- 0
App.vue View File

@ -8,6 +8,7 @@
export default {
onLaunch: function() {
this.initApp()
// this.$store.commit('initConfig')
},
methods: {
//


+ 12
- 0
api/system/configList.js View File

@ -0,0 +1,12 @@
// 小程序-配置信息
import request from '@/utils/request'
// 小程序-配置信息列表数据查询
export function getConfigList() {
return request({
'url': '/applet/config/configList',
'method': 'get'
}).then(res => {
return res.data
})
}

+ 39
- 0
api/system/task.js View File

@ -0,0 +1,39 @@
import request from '@/utils/request'
// 获取任务列表
export function getTaskList(data={}){
return request({
url: `/h5/task/list`,
headers:{ "isToken":true},
method: 'get',
params: data
})
}
// 接受任务
export function acceptTask(taskId){
return request({
url: `/h5/task/accept/${taskId}`,
headers:{ "isToken":true},
method: 'post'
})
}
// 提交任务
export function submitTask(data){
return request({
url: `/h5/task/submit`,
headers:{ "isToken":true},
method: 'post',
data
})
}
// 获取任务详情
export function getTaskDetail(taskId){
return request({
url: `/h5/task/detail/${taskId}`,
headers:{ "isToken":true},
method: 'get'
})
}

+ 5
- 0
main.js View File

@ -5,10 +5,15 @@ import plugins from './plugins' // plugins
import './permission' // permission
import share from 'utils/share.js'//share.js
import uView from '@/uni_modules/uview-ui'
import configListMinxin from '@/mixins/configList.js'
Vue.use(uView)
Vue.use(plugins)
Vue.mixin(configListMinxin)
Vue.config.productionTip = false
Vue.prototype.$store = store


+ 61
- 0
mixins/configList.js View File

@ -0,0 +1,61 @@
import { mapState } from 'vuex'
export default {
data() {
return {
// 默认的全局分享内容
Gshare: {
// title: '三只青蛙',
path: '/pages_order/auth/wxLogin', // 全局分享的路径,比如 首页
// imageUrl: '/static/image/login/logo.png', // 全局分享的图片(可本地可网络)
}
}
},
computed: {
...mapState(['configList', 'userInfo', 'riceInfo']),
currentPagePath() {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
let path = `/${currentPage.route}`;
// 获取当前页面的参数
const options = currentPage.options;
if (options && Object.keys(options).length > 0) {
const params = this.$utils.objectToUrlParams(options);
path += `?${params}`;
}
return path;
},
},
// 定义全局分享
// 1.发送给朋友
onShareAppMessage(res) {
let o = {
title : this.configList.logo_name,
...this.Gshare,
}
if(this.userInfo.id){
if(this.Gshare.path.includes('?')){
o.path += '&shareId=' + this.userInfo.id
}else{
o.path += '?shareId=' + this.userInfo.id
}
}
return o
},
//2.分享到朋友圈
onShareTimeline(res) {
let o = {
...this.Gshare,
title : this.configList.logo_name,
}
if(this.userInfo.id){
o.path = this.Gshare.path + '?shareId=' + this.userInfo.id
}
return o
},
methods: {
}
}

+ 78
- 0
mixins/list.js View File

@ -0,0 +1,78 @@
/**
* 处理查询参数
* @param {Object} self - 组件实例
* @param {Object} queryParams - 额外的查询参数
* @returns {Object} 合并后的查询参数
*/
function query(self, queryParams){
// 深度合并对象
return self.$utils.deepMergeObject(
self.$utils.deepMergeObject(self.queryParams,
(self.beforeGetData && self.beforeGetData()) || {}),
queryParams)
}
/**
* 列表数据加载混入
* 提供列表数据的加载分页下拉刷新上拉加载更多等功能
*/
export default {
data() {
return {
queryParams: {
pageNo: 1,
pageSize: 10,
},
total : 0,
list : [],
}
},
// 下拉刷新
onPullDownRefresh() {
this.getData()
},
// 上拉加载更多
onReachBottom() {
this.loadMoreData()
},
// 页面显示时加载数据
onShow() {
this.getData()
},
methods: {
/**
* 获取列表数据
* @param {Object} queryParams - 查询参数
* @returns {Promise} 返回Promise对象
*/
getData(queryParams){
return new Promise((success, error) => {
if(!this.mixinsListApi){
return console.error('mixinsListApi 缺失');
}
this.$api(this.mixinsListApi,
query(this, queryParams), res => {
uni.stopPullDownRefresh()
if(res.code == 200){
success(res.result)
// 更新列表数据
this[this.mixinsListKey || 'list'] = res.result.records || res.result
// 更新总数
this.total = res.result.total || res.result.length
// 调用数据加载完成的回调
this.getDataThen && this.getDataThen(res.result.records, res.result.total, res.result)
}
})
})
},
/**
* 加载更多数据
*/
loadMoreData(){
if(this.queryParams.pageSize < this.total){
this.queryParams.pageSize += 10
this.getData()
}
},
}
}

+ 36
- 0
pages.json View File

@ -149,6 +149,15 @@
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/personalCenter/orderDetail",
"style": {
"navigationBarTitleText": "当前订单",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/personalCenter/pet",
"style": {
@ -292,6 +301,33 @@
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/personalCenter/taskList",
"style": {
"navigationBarTitleText": "任务中心",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/personalCenter/taskDetail",
"style": {
"navigationBarTitleText": "任务详情",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/personalCenter/userInfo",
"style": {
"navigationBarTitleText": "个人信息",
"navigationBarBackgroundColor": "#FFBF60",
"enablePullDownRefresh": false,
"navigationBarTextStyle": "white"
}
}
],
"tabBar": {


+ 120
- 0
pages/components/NewUserCoupon.vue View File

@ -0,0 +1,120 @@
<template>
<view class="new-user-coupon" v-if="showPopup">
<view class="mask" @click="closePopup"></view>
<view class="coupon-container">
<view class="coupon-content">
<image class="coupon-image" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/coupon/new-user-coupon.png" mode="widthFix"></image>
<view class="coupon-btn" @click="getCoupon">立即领取</view>
</view>
<view class="close-btn" @click="closePopup">
<text class="close-icon">×</text>
</view>
</view>
</view>
</template>
<script>
import { getToken } from '@/utils/auth'
export default {
name: 'NewUserCoupon',
data() {
return {
showPopup: true
}
},
methods: {
closePopup() {
this.showPopup = false
this.$emit('close')
},
getCoupon() {
//
if (!getToken()) {
//
uni.navigateTo({
url: '/pages/login/index'
})
} else {
//
this.$emit('getCoupon')
this.closePopup()
}
}
}
}
</script>
<style lang="scss">
.new-user-coupon {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
display: flex;
align-items: center;
justify-content: center;
.mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
}
.coupon-container {
position: relative;
width: 560rpx;
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center;
}
.coupon-content {
display: flex;
flex-direction: column;
align-items: center;
}
.coupon-image {
width: 560rpx;
border-radius: 20rpx;
}
.coupon-btn {
margin-top: -80rpx;
width: 300rpx;
height: 80rpx;
background: #FFB13F;
color: #FFFFFF;
font-size: 32rpx;
font-weight: bold;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn {
margin-top: 40rpx;
width: 60rpx;
height: 60rpx;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.close-icon {
color: #666;
font-size: 40rpx;
font-weight: bold;
}
}
}
</style>

+ 32
- 1
pages/index.vue View File

@ -1,6 +1,9 @@
<template>
<view class="home-content">
<view class="banner">
<NewUserCoupon v-if="isNewUser" @close="closeNewUserPopup" @getCoupon="handleGetCoupon"></NewUserCoupon>
<view class="banner">
<uni-swiper-dot class="uni-swiper-dot-box" field="content">
<swiper class="swiper" indicator-dots indicator-color="rgba(255, 255, 255, 0.50)"
indicator-active-color="#ffffff" autoplay interval="5000">
@ -325,6 +328,7 @@
} from '@/utils/auth'
import Kefu from './common/kefu.vue'
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue';
import NewUserCoupon from './components/NewUserCoupon.vue';
export default {
data() {
return {
@ -340,6 +344,7 @@
couponList: [],
showMask: false,
envVersion: 'develop',
isNewUser: false,
selectedDate: [],
startDate: '',
endDate: '',
@ -387,11 +392,37 @@
components: {
Kefu,
uniPopup,
NewUserCoupon
},
mounted() {
this.getCalendarDate();
this.checkNewUser();
},
methods: {
checkNewUser() {
//
// 访
const isFirstVisit = !uni.getStorageSync('hasVisited');
if (isFirstVisit) {
this.isNewUser = true;
// 访
uni.setStorageSync('hasVisited', true);
}
},
closeNewUserPopup() {
this.isNewUser = false;
},
handleGetCoupon() {
//
if (getToken()) {
this.getCoupon();
} else {
//
uni.navigateTo({
url: '/pages/personalCenter/index'
});
}
},
getAllCoupon() {
uni.navigateTo({
url: '/pages/companionPetList/couponList'


+ 466
- 447
pages/newOrder/petList.vue View File

@ -1,464 +1,483 @@
<template>
<view class="personal-pet">
<view v-if="petList.length>0" class="personal-pet-list">
<view v-for="(item,index) in petList" :key="index">
<view :class="['personal-pet-list-item',item.gender=='男生'?'.personal-pet-list-item_backgroud_m':'.personal-pet-list-item_backgroud_f' ]">
<view class="personal-pet-info">
<view>
<u-avatar :src="item.photo?item.photo:defaultPhoto" size="60" shape="circle"></u-avatar>
</view>
<view class="personal-pet-info-1" style="width: calc(100% - 120px);">
<view class="personal-pet-info-2">
<view class="personal-pet-name ellipsis">
{{item.name}}
</view>
<view class="personal-pet-sex">
<img :src="item.gender=='男生'?'../../static/images/details/boy.svg':'../../static/images/details/girl.svg'" alt="sex"
style="width: 16px;height: 16px;"/>
</view>
</view>
<view class="personal-pet-info-3" style="width: 100%;">
<view class="ellipsis" style="max-width: 25%;" >
{{item.breed || '未知'}}
</view>
<view class="personal-pet-info-age" style="max-width: 90px;">
{{item.birthDate || '未知'}}
</view>
<view class="ellipsis" style="max-width: 25%;">
{{item.bodyType || '未知'}}
</view>
</view>
</view>
<view style="margin-left: auto; width: 20px;">
<u-checkbox-group v-model="item.checked">
<u-checkbox shape="circle" activeColor="#ffbf60"></u-checkbox>
</u-checkbox-group>
</view>
</view>
<view class="personal-pet-info-disposition ellipsis">
性格 {{item.personality}}
</view>
<view class="personal-pet-info-btns">
<view style="display: flex; align-items: center;">
<u-button v-if="item.selectedDate.length==0" color="#FFBF60" size="mini" iconPlacement="right" shape="circle" plain @click="selectDate(item)">
<view style="font-size: 14px;">请选择服务日期</view>
<u-icon name="arrow-right" color="#FFBF60" size="14" style="margin-right: 5px;"></u-icon>
</u-button>
<u-button v-else color="#FFBF60" type="primary" size="mini" iconPlacement="right" shape="circle" @click="selectDate(item)">
<view style="font-size: 14px;">{{ showSelectedDate(item.selectedDate) }}</view>
<u-icon name="arrow-right" color="#ffffff" size="14" style="margin-right: 5px;"></u-icon>
</u-button>
</view>
<view style="display: flex; align-items: center; justify-content: flex-end;">
<view class="personal-pet-info-btn" @click="editPet(item)">
<u-icon name="edit-pen" color="#7d8196" size="16" style="margin-right: 5px;"></u-icon>
<view style="margin-left: 5px;">
编辑
</view>
</view>
<view class="personal-pet-info-btn" @click="deletePet(item)">
<u-icon name="trash" color="#7d8196" size="16"></u-icon>
<view style="margin-left: 5px;">
删除
<view class="personal-pet">
<view v-if="petList.length > 0" class="personal-pet-list">
<view v-for="(item, index) in petList" :key="index">
<view
:class="['personal-pet-list-item', item.gender == '男生' ? '.personal-pet-list-item_backgroud_m' : '.personal-pet-list-item_backgroud_f']">
<view class="personal-pet-info">
<view>
<u-avatar :src="item.photo ? item.photo : defaultPhoto" size="60" shape="circle"></u-avatar>
</view>
<view class="personal-pet-info-1" style="width: calc(100% - 120px);">
<view class="personal-pet-info-2">
<view class="personal-pet-name ellipsis">
{{ item.name }}
</view>
<view class="personal-pet-sex">
<img :src="item.gender == '男生' ? '../../static/images/details/boy.svg' : '../../static/images/details/girl.svg'"
alt="sex" style="width: 16px;height: 16px;" />
</view>
</view>
<view class="personal-pet-info-3" style="width: 100%;">
<view class="ellipsis" style="max-width: 25%;">
{{ item.breed || '未知' }}
</view>
<view class="personal-pet-info-age" style="max-width: 90px;">
{{ item.birthDate || '未知' }}
</view>
<view class="ellipsis" style="max-width: 25%;">
{{ item.bodyType || '未知' }}
</view>
</view>
</view>
<view style="margin-left: auto; width: 20px;">
<u-checkbox-group v-model="item.checked">
<u-checkbox shape="circle" activeColor="#ffbf60"></u-checkbox>
</u-checkbox-group>
</view>
</view>
<view class="personal-pet-info-disposition ellipsis">
性格 {{ item.personality }}
</view>
<view class="personal-pet-info-btns">
<view style="display: flex; align-items: center;">
<u-button v-if="item.selectedDate.length == 0" color="#FFBF60" size="mini"
iconPlacement="right" shape="circle" plain @click="selectDate(item)">
<view style="font-size: 14px;">请选择服务日期</view>
<u-icon name="arrow-right" color="#FFBF60" size="14"
style="margin-right: 5px;"></u-icon>
</u-button>
<u-button v-else color="#FFBF60" type="primary" size="mini" iconPlacement="right"
shape="circle" @click="selectDate(item)">
<view style="font-size: 14px;">{{ showSelectedDate(item.selectedDate) }}</view>
<u-icon name="arrow-right" color="#ffffff" size="14"
style="margin-right: 5px;"></u-icon>
</u-button>
</view>
<view style="display: flex; align-items: center; justify-content: flex-end;">
<view class="personal-pet-info-btn" @click="editPet(item)">
<u-icon name="edit-pen" color="#7d8196" size="16" style="margin-right: 5px;"></u-icon>
<view style="margin-left: 5px;">
编辑
</view>
</view>
<view class="personal-pet-info-btn" @click="deletePet(item)">
<u-icon name="trash" color="#7d8196" size="16"></u-icon>
<view style="margin-left: 5px;">
删除
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view v-else class="personal-pet-none">
<img src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/catdog.png" alt="pet"
style="width: 149px;height: 124px;" mode="widthFix" />
<view class="personal-pet-none-text">这里还没有您的宠物,请点击添加吧~</view>
</view>
<view class="personal-pet-add">
<view style="width: 45%;">
<u-button class="personal-pet-add-btn" color="#FFF4E4" @click="addPet">
<view style="font-size: 32rpx;font-weight: 500;color: #FFAA48;">
新增宠物
</view>
</u-button>
</view>
<view style="width: 45%;">
<u-button class="personal-pet-add-btn" color="#FFBF60" @click="confirm">
<view style="font-size: 32rpx;font-weight: 500;color: #fff;">
确定
</view>
</u-button>
</view>
</view>
<view class="">
<u-picker :showToolbar='false' :show="show" :columns="petTypes" @change="petTypeChange" @cancel="cancel"
@confirm="confirmPetType"></u-picker>
</view>
<u-modal :show="showDel" @confirm="confirmDel" @cancel="cancelDel" ref="uModal" showCancelButton
:asyncClose="true" :content='delContent'>
</u-modal>
<view v-if="showCalendar" class="calendar-popup">
<view class="calendar-mask"></view>
<view class="calendar-content">
<uni-calendar class="uni-calendar--hook" :selected="selectedDate" :startDate="startDate"
:endDate="endDate" @change="change" :showMonth="false" />
<u-button color="#FFBF60" type="primary" @click="confirmCanlendar">确定</u-button>
</view>
</view>
</view>
</view>
</view>
<view v-else class="personal-pet-none">
<img src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/catdog.png" alt="pet"
style="width: 149px;height: 124px;" mode="widthFix"/>
<view class="personal-pet-none-text">这里还没有您的宠物,请点击添加吧~</view>
</view>
<view class="personal-pet-add">
<view style="width: 45%;">
<u-button class="personal-pet-add-btn" color="#FFF4E4" @click="addPet">
<view style="font-size: 32rpx;font-weight: 500;color: #FFAA48;">
新增宠物
</view>
</u-button>
</view>
<view style="width: 45%;">
<u-button class="personal-pet-add-btn" color="#FFBF60" @click="confirm">
<view style="font-size: 32rpx;font-weight: 500;color: #fff;">
确定
</view>
</u-button>
</view>
</view>
<view class="">
<u-picker
:showToolbar='false'
:show="show"
:columns="petTypes"
@change="petTypeChange"
@cancel="cancel"
@confirm="confirmPetType"
></u-picker>
</view>
<u-modal :show="showDel"
@confirm="confirmDel"
@cancel="cancelDel"
ref="uModal"
showCancelButton
:asyncClose="true"
:content='delContent'>
</u-modal>
<view v-if="showCalendar" class="calendar-popup">
<view class="calendar-mask" ></view>
<view class="calendar-content">
<uni-calendar class="uni-calendar--hook" :selected="selectedDate" :startDate="startDate" :endDate="endDate"
@change="change" :showMonth="false" />
<u-button color="#FFBF60" type="primary" @click="confirmCanlendar">确定</u-button>
</view>
</view>
</view>
</view>
</template>
<script>
import { getDictList } from "@/api/system/user.js"
import { getPetList,delPet } from "@/api/system/pet"
export default{
data(){
return{
defaultPhoto:'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/catdog.png',
petList:[],
show:false,
showDel:false,
petTypes:[],
delContent:'',
deleteId:'',
petType:'',
startDate: '',
endDate: '',
showCalendar: false,
selectedDate:[],
currentPets:[]
}
},
onShow() {
this.getPetList();
},
onPullDownRefresh() {
this.getPetList();
},
mounted() {
this.getPetTypeList();
this.getPetList();
this.getCalendarDate();
},
methods:{
getPetList(){
this.petList=[]
getPetList().then(res=>{
let currentPets = this.$globalData.newOrderData.currentPets
if(res&&res.content){
this.petList=res.content.map(e=>{
const currentPet = currentPets.find(item => item.id === e.id)
if(currentPet) {
e.selectedDate = currentPet.selectedDate
e.checked = currentPet.checked
} else {
e.selectedDate = []
e.checked = false
}
return e
})
this.showDel = false;
}
})
},
getPetTypeList(){
getDictList('pet_type').then(res=>{
if (res.code == 200) {
let petType = res.data.map(e=>e.dictLabel)
this.petTypes=[petType]
console.log(this.petTypes)
} else {
this.$modal.showToast('获取pet type失败')
}
})
},
addPet(){
this.show = true;
},
cancel() {
this.show = false
},
petTypeChange(e){
console.log(e)
this.petType=e.value[0]
},
confirmPetType(e) {
console.log(e)
this.show = false
// ,使petType,使e.value[0]
const type = this.petType || e.value[0]
//
const petTypeMap = {
'猫猫': 'cat',
'狗狗': 'dog'
}
const petType = petTypeMap[type]
if(petType) {
uni.navigateTo({
url: `/pages/personalCenter/petInfo?petType=${petType}&optionType=add&isNewOrder=true`
});
}
},
editPet(item){
const petTypeMap = {
'猫猫': 'cat',
'cat': 'cat',
'狗狗': 'dog',
'dog': 'dog'
}
const petType = petTypeMap[item.petType]
if(petType) {
uni.navigateTo({
url: `/pages/personalCenter/petInfo?petType=${petType}&optionType=edit&petId=${item.id}&isNewOrder=true`
});
}
},
deletePet(item){
this.delContent = "确定要删除"+item.name+'?';
this.showDel = true;
this.deleteId=item.id;
},
confirmDel(){
delPet(this.deleteId).then(res=>{
console.log(res);
this.$modal.showToast('删除成功');
this.getPetList()
})
},
cancelDel(){
this.showDel = false;
this.deleteId='';
},
getCalendarDate() {
let tomorrow = new Date()
tomorrow.setDate(tomorrow.getDate());
this.startDate = this.formatDate(tomorrow);
//
let threeMonthsLater = new Date();
threeMonthsLater.setMonth(threeMonthsLater.getMonth() + 3);
this.endDate = this.formatDate(threeMonthsLater);
},
formatDate(date) {
let year = date.getFullYear();
let month = (date.getMonth() + 1).toString().padStart(2, '0');
let day = date.getDate().toString().padStart(2, '0');
return year + '-' + month + '-' + day;
},
selectDate(item){
this.currentId= item.id
this.selectedDate = item.selectedDate
this.showCalendar = true;
},
confirmCanlendar(){
this.petList.find(e=>e.id==this.currentId).selectedDate = this.selectedDate.sort((a,b) => new Date(a.date) - new Date(b.date))
this.selectedDate = []
this.showCalendar = false;
},
change(e){
const selectedValue = this.selectedDate.find(item => item.date === e.fulldate)
if (selectedValue) {
//
this.selectedDate = this.selectedDate.filter(item => item.date !== e.fulldate);
} else {
this.selectedDate.push({
date: e.fulldate,
info: '预定'
})
}
},
confirm(){
const selectedPets = this.petList.filter(e=>e.checked)
const unselectedDatePets = selectedPets.filter(pet => !pet.selectedDate || pet.selectedDate.length === 0)
if(unselectedDatePets.length > 0) {
const petNames = unselectedDatePets.map(pet => pet.name).join('、')
this.$modal.showToast(`请为${petNames}选择服务时间`)
return
import { getDictList } from "@/api/system/user.js"
import { getPetList, delPet } from "@/api/system/pet"
export default {
data() {
return {
defaultPhoto: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/pet/catdog.png',
petList: [],
show: false,
showDel: false,
petTypes: [],
delContent: '',
deleteId: '',
petType: '',
startDate: '',
endDate: '',
showCalendar: false,
selectedDate: [],
currentPets: []
}
this.$globalData.newOrderData.currentPets = selectedPets
uni.redirectTo({
url: '/pages/newOrder/serviceNew'
})
},
showSelectedDate(selectedDate){
// ...
// 01/01...01/07
const firstDate = selectedDate[0].date.substring(5).replace('-', '/')
const lastDate = selectedDate[selectedDate.length - 1].date.substring(5).replace('-', '/')
const days = selectedDate.length
return `${firstDate}${days>2?'...':''}${days==2?',':''}${days>1?lastDate:''}${days}`
}
}
}
},
onShow() {
this.getPetList();
},
onPullDownRefresh() {
this.getPetList();
},
mounted() {
this.getPetTypeList();
this.getPetList();
this.getCalendarDate();
},
methods: {
getPetList() {
this.petList = []
getPetList().then(res => {
let currentPets = this.$globalData.newOrderData.currentPets
if (res && res.content) {
this.petList = res.content.map(e => {
const currentPet = currentPets.find(item => item.id === e.id)
if (currentPet) {
e.selectedDate = currentPet.selectedDate
e.checked = currentPet.checked
} else {
e.selectedDate = []
e.checked = false
}
return e
})
this.showDel = false;
}
})
},
getPetTypeList() {
getDictList('pet_type').then(res => {
if (res.code == 200) {
let petType = res.data.map(e => e.dictLabel)
this.petTypes = [petType]
console.log(this.petTypes)
} else {
this.$modal.showToast('获取pet type失败')
}
})
},
addPet() {
this.show = true;
},
cancel() {
this.show = false
},
petTypeChange(e) {
console.log(e)
this.petType = e.value[0]
},
confirmPetType(e) {
console.log(e)
this.show = false
// ,使petType,使e.value[0]
const type = this.petType || e.value[0]
//
const petTypeMap = {
'猫猫': 'cat',
'狗狗': 'dog'
}
const petType = petTypeMap[type]
if (petType) {
uni.navigateTo({
url: `/pages/personalCenter/petInfo?petType=${petType}&optionType=add&isNewOrder=true`
});
}
},
editPet(item) {
const petTypeMap = {
'猫猫': 'cat',
'cat': 'cat',
'狗狗': 'dog',
'dog': 'dog'
}
const petType = petTypeMap[item.petType]
if (petType) {
uni.navigateTo({
url: `/pages/personalCenter/petInfo?petType=${petType}&optionType=edit&petId=${item.id}&isNewOrder=true`
});
}
},
deletePet(item) {
this.delContent = "确定要删除" + item.name + '?';
this.showDel = true;
this.deleteId = item.id;
},
confirmDel() {
delPet(this.deleteId).then(res => {
console.log(res);
this.$modal.showToast('删除成功');
this.getPetList()
})
},
cancelDel() {
this.showDel = false;
this.deleteId = '';
},
getCalendarDate() {
let tomorrow = new Date()
tomorrow.setDate(tomorrow.getDate());
this.startDate = this.formatDate(tomorrow);
//
let threeMonthsLater = new Date();
threeMonthsLater.setMonth(threeMonthsLater.getMonth() + 3);
this.endDate = this.formatDate(threeMonthsLater);
},
formatDate(date) {
let year = date.getFullYear();
let month = (date.getMonth() + 1).toString().padStart(2, '0');
let day = date.getDate().toString().padStart(2, '0');
return year + '-' + month + '-' + day;
},
selectDate(item) {
this.currentId = item.id
this.selectedDate = item.selectedDate
this.showCalendar = true;
},
confirmCanlendar() {
this.petList.find(e => e.id == this.currentId).selectedDate = this.selectedDate.sort((a, b) => new Date(a.date) - new Date(b.date))
this.selectedDate = []
this.showCalendar = false;
},
change(e) {
const selectedValue = this.selectedDate.find(item => item.date === e.fulldate)
if (selectedValue) {
//
this.selectedDate = this.selectedDate.filter(item => item.date !== e.fulldate);
} else {
this.selectedDate.push({
date: e.fulldate,
info: '预定'
})
}
},
confirm() {
const selectedPets = this.petList.filter(e => e.checked)
const unselectedDatePets = selectedPets.filter(pet => !pet.selectedDate || pet.selectedDate.length === 0)
if (unselectedDatePets.length > 0) {
const petNames = unselectedDatePets.map(pet => pet.name).join('、')
this.$modal.showToast(`请为${petNames}选择服务时间`)
return
}
this.$globalData.newOrderData.currentPets = selectedPets
uni.redirectTo({
url: '/pages/newOrder/serviceNew'
})
},
showSelectedDate(selectedDate) {
// ...
// 01/01...01/07
const firstDate = selectedDate[0].date.substring(5).replace('-', '/')
const lastDate = selectedDate[selectedDate.length - 1].date.substring(5).replace('-', '/')
const days = selectedDate.length
return `${firstDate}${days > 2 ? '...' : ''}${days == 2 ? ',' : ''}${days > 1 ? lastDate : ''}${days}`
}
}
}
</script>
<style lang="scss">
.personal-pet{
position: relative;
height: 100%;
padding-bottom: 90px;
.personal-pet-add{
background-color: #FFFFFF;
padding:10px 20px 40px;
width: 100%;
height: 90px;
position: fixed;
bottom: 0;
z-index: 100;
display: flex;
justify-content: space-between;
.personal-pet-add-btn{
width: 100%;
border-radius: 6px;
font-size: 16px;
}
}
.personal-pet-list{
.personal-pet-list-add{
width: 100%;
height: 44px;
background-color: #FFFFFF;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 15px;
.personal-pet-list-add-btn{
font-size: 14px;
color:#AAA;
display: flex;
align-items: center;
}
}
.personal-pet-list-item_backgroud_m{
background: linear-gradient(179deg, #EDF5FE 0.75%, #FFF 34.11%);
}
.personal-pet-list-item_backgroud_f{
background: linear-gradient(179deg, #FFF4F6 0.75%, #FFF 34.11%);
}
.personal-pet-list-item{
margin: 10px 10px 0 10px;
border-radius: 5px;
padding: 20px 10px 10px;
.personal-pet-info{
display: flex;
align-items: center;
justify-content: flex-start;
.personal-pet-info-1{
margin-left: 10px;
.personal-pet-info-2{
display: flex;
flex-wrap: wrap;
.personal-pet-name{
color: #333;
font-size: 16px;
margin-right: 10px;
}
}
.personal-pet-info-3 {
display: flex;
align-items: baseline;
font-size: 14px;
margin-top: 5px;
color: #7D8196;
.personal-pet-info-age{
padding: 0 10px;
margin: 0 10px;
border-left: solid 2px #7D8196;
border-right: solid 2px #7D8196;
}
}
}
}
.personal-pet-info-disposition{
padding: 10px;
color: #7D8196;
font-size: 14px;
background: #f9f9f9;
border-radius: 5px;
margin-top: 10px;
}
.personal-pet-info-btns{
display: flex;
justify-content: space-between;
margin-top: 10px;
.personal-pet-info-btn{
display: flex;
font-size: 14px;
color: #7D8196;
margin-left: 20px;
}
}
}
}
.personal-pet-none{
display:flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin-top: 40%;
.personal-pet-none-text{
color: #666;
text-align: center;
font-size: 14px;
width: 100%;
margin-top: 10px;
}
}
.calendar-popup{
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
.calendar-content{
background: #fff;
border-radius: 16px 16px 0 0;
padding: 0 20px 20px;
}
<style lang="scss" scoped>
.personal-pet {
position: relative;
height: 100%;
padding-bottom: 90px;
.personal-pet-add {
background-color: #FFFFFF;
padding: 10px 20px 40px;
width: 100%;
height: 90px;
position: fixed;
bottom: 0;
z-index: 100;
display: flex;
justify-content: space-between;
.personal-pet-add-btn {
width: 100%;
border-radius: 6px;
font-size: 16px;
}
}
.personal-pet-list {
.personal-pet-list-add {
width: 100%;
height: 44px;
background-color: #FFFFFF;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 15px;
.personal-pet-list-add-btn {
font-size: 14px;
color: #AAA;
display: flex;
align-items: center;
}
}
.personal-pet-list-item_backgroud_m {
background: linear-gradient(179deg, #EDF5FE 0.75%, #FFF 34.11%);
}
.personal-pet-list-item_backgroud_f {
background: linear-gradient(179deg, #FFF4F6 0.75%, #FFF 34.11%);
}
.personal-pet-list-item {
margin: 10px 10px 0 10px;
border-radius: 5px;
padding: 20px 10px 10px;
.personal-pet-info {
display: flex;
align-items: center;
justify-content: flex-start;
.personal-pet-info-1 {
margin-left: 10px;
.personal-pet-info-2 {
display: flex;
flex-wrap: wrap;
.personal-pet-name {
color: #333;
font-size: 16px;
margin-right: 10px;
}
}
.personal-pet-info-3 {
display: flex;
align-items: baseline;
font-size: 14px;
margin-top: 5px;
color: #7D8196;
.personal-pet-info-age {
padding: 0 10px;
margin: 0 10px;
border-left: solid 2px #7D8196;
border-right: solid 2px #7D8196;
}
}
}
}
.personal-pet-info-disposition {
padding: 10px;
color: #7D8196;
font-size: 14px;
background: #f9f9f9;
border-radius: 5px;
margin-top: 10px;
}
.personal-pet-info-btns {
display: flex;
justify-content: space-between;
margin-top: 10px;
.personal-pet-info-btn {
display: flex;
font-size: 14px;
color: #7D8196;
margin-left: 20px;
}
}
}
}
.personal-pet-none {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
margin-top: 40%;
.personal-pet-none-text {
color: #666;
text-align: center;
font-size: 14px;
width: 100%;
margin-top: 10px;
}
}
.calendar-popup {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
.calendar-content {
background: #fff;
border-radius: 16px 16px 0 0;
padding: 0 20px 20px;
}
}
.calendar-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5); /* 半透明黑色 */
z-index: 998; /* 确保在内容下方 */
pointer-events: none; /* 使遮罩层不阻止点击事件 */
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
/* 半透明黑色 */
z-index: 998;
/* 确保在内容下方 */
pointer-events: none;
/* 使遮罩层不阻止点击事件 */
}
.calendar-content {
position: relative; /* 确保内容在遮罩层之上 */
z-index: 999; /* 确保内容在遮罩层之上 */
position: relative;
/* 确保内容在遮罩层之上 */
z-index: 999;
/* 确保内容在遮罩层之上 */
}
}
}
</style>

+ 39
- 5
pages/personalCenter/index.vue View File

@ -2,7 +2,7 @@
<view class="personal-index">
<view class="personal-base-info">
<view style="width: 100%;height: 140px; margin-top: 10px;">
<view style="width: 100%;text-align: center;">
<view style="width: 100%;text-align: center;" @click="openOtherPage('userInfo')">
<img src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/avatar_1.png"
style="width: 66px; height: 66px;" mode="widthFix"></img>
</view>
@ -14,7 +14,7 @@
<view class="personal-user-level">
{{userLevel}}
</view>
<view class="personal-user-name">
<view class="personal-user-name" @click="openOtherPage('userInfo')">
{{userName}}
</view>
<view style="width: 62px;margin-left: 15px;"> </view>
@ -109,7 +109,8 @@
margin-bottom: 10px;">
当前订单
</view>
<uni-steps :options="orderStatus" active-icon="checkbox" :active="active" active-color='#ffaa48' />
<uni-steps :options="orderStatus" active-icon="checkbox" :active="active" active-color='#ffaa48'
@clickStep="handleStepClick" />
</view>
<view class="personal-order-footer" slot="actions">
<view class="personal-order-footer-text">
@ -214,7 +215,12 @@
icon:'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/lock.png',
label: '门锁信息',
path:'lock'
}
},
{
icon:'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/lock.png',
label: '任务中心',
path:'taskList'
},
]
}
@ -355,10 +361,24 @@
toRegister(){
this.$modal.showToast('敬请期待');
},
handleStepClick(e) {
// if(!this.isMember){
// this.$modal.showToast('');
// return;
// }
// 2
// if (e.index === 2 && this.active === 2) {
//
uni.navigateTo({
url: `/pages/personalCenter/orderDetail`
});
// }
},
openOtherPage(type){
if(!this.isMember){
this.$modal.showToast('暂未匹配到您的会员信息,请先注册成为会员!');
return;s
return;
}
switch(type){
case 'member':{
@ -422,6 +442,18 @@
});
break;
}
case 'taskList':{
uni.navigateTo({
url: `/pages/personalCenter/taskList`
});
break;
}
case 'userInfo':{
uni.navigateTo({
url: `/pages/personalCenter/userInfo`
});
break;
}
default:this.$modal.showToast('页面开发中');
}
@ -628,6 +660,8 @@
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 30rpx 0;
.personal-function{
width: 24%;
display: flex;


+ 332
- 0
pages/personalCenter/orderDetail.vue View File

@ -0,0 +1,332 @@
<template>
<view class="order-detail-container">
<!-- 日期信息 -->
<view class="order-date">
<view class="order-date-icon">
<u-icon name="calendar" color="#FFAA48" size="20"></u-icon>
</view>
<view class="order-date-text">{{ orderDate }}</view>
</view>
<!-- 个人信息区域 -->
<view class="order-section">
<view class="order-section-title">个人信息</view>
<view class="order-section-content">
<view class="order-image-item">
<view class="order-image-label">手套照片 (共2张)</view>
<view class="order-image-container">
<image class="order-image" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/gloves.jpg" mode="aspectFill"></image>
</view>
</view>
<view class="order-image-item">
<view class="order-image-label">鞋套照片 (共2张)</view>
<view class="order-image-container">
<image class="order-image" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/shoes.jpg" mode="aspectFill"></image>
</view>
</view>
</view>
</view>
<!-- 宠物状况记录 -->
<view class="order-section">
<view class="order-section-title">宠物状况记录</view>
<view class="order-section-content">
<view class="pet-record" v-for="(pet, index) in pets" :key="index">
<view class="pet-name">{{ pet.name }} ({{ pet.photoCount }})</view>
<view class="pet-images">
<view class="pet-image-container" v-for="(photo, photoIndex) in pet.photos" :key="photoIndex">
<image class="pet-image" :src="photo" mode="aspectFill"></image>
</view>
</view>
</view>
</view>
</view>
<!-- 基础服务记录 -->
<view class="order-section">
<view class="order-section-title">基础服务记录</view>
<view class="order-section-content">
<view class="service-record" v-for="(service, index) in services" :key="index">
<view class="service-name">{{ service.name }}</view>
<view class="service-comparison">
<view class="service-before-after">
<view class="service-image-container">
<image class="service-image" :src="service.beforeImage" mode="aspectFill"></image>
<view class="service-image-label"></view>
</view>
<view class="service-image-container">
<image class="service-image" :src="service.afterImage" mode="aspectFill"></image>
<view class="service-image-label"></view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 定制服务记录 -->
<view class="order-section">
<view class="order-section-title">定制服务记录</view>
<view class="order-section-content">
<view class="custom-service">
<view class="custom-service-item">
<view class="custom-service-name">遛狗 (5-30分钟)</view>
<view class="custom-service-images">
<image class="custom-service-image" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/dog1.jpg" mode="aspectFill"></image>
<image class="custom-service-image" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/dog2.jpg" mode="aspectFill"></image>
<image class="custom-service-image" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/dog3.jpg" mode="aspectFill"></image>
</view>
</view>
<view class="custom-service-item">
<view class="custom-service-name">梳毛 (5-30分钟)</view>
<view class="custom-service-images">
<image class="custom-service-image" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/cat1.jpg" mode="aspectFill"></image>
<image class="custom-service-image" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/cat2.jpg" mode="aspectFill"></image>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
orderId: null,
orderDate: '2024年12月08日',
pets: [
{
name: '小汪',
photoCount: 2,
photos: [
'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/dog1.jpg',
'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/dog2.jpg'
]
},
{
name: 'Billion',
photoCount: 2,
photos: [
'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/cat1.jpg',
'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/cat2.jpg'
]
}
],
services: [
{
name: '猫粮前后对比',
beforeImage: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/food_before.jpg',
afterImage: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/food_after.jpg'
},
{
name: '水碗前后对比',
beforeImage: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/water_before.jpg',
afterImage: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/water_after.jpg'
},
{
name: '猫砂盆 厚度前后对比',
beforeImage: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/litter_before.jpg',
afterImage: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/litter_after.jpg'
}
]
}
},
onLoad(options) {
if (options.orderId) {
this.orderId = options.orderId;
// orderId
this.loadOrderDetail();
}
},
methods: {
loadOrderDetail() {
// API
// API
/*
getOrderDetail(this.orderId).then(res => {
if (res && res.code === 200) {
this.orderDate = res.data.orderDate;
this.pets = res.data.pets;
this.services = res.data.services;
}
});
*/
}
}
}
</script>
<style lang="scss">
.order-detail-container {
padding: 15px;
background-color: #f5f5f7;
min-height: 100vh;
}
.order-date {
display: flex;
align-items: center;
padding: 10px 15px;
background-color: #fff;
border-radius: 8px;
margin-bottom: 15px;
.order-date-icon {
margin-right: 10px;
}
.order-date-text {
color: #333;
font-size: 16px;
font-weight: 500;
}
}
.order-section {
background-color: #fff;
border-radius: 8px;
margin-bottom: 15px;
overflow: hidden;
.order-section-title {
padding: 12px 15px;
border-bottom: 1px solid #f0f0f0;
color: #333;
font-size: 16px;
font-weight: 500;
position: relative;
&::before {
content: '';
position: absolute;
left: 0;
top: 12px;
height: 16px;
width: 4px;
background-color: #ffaa48;
border-radius: 0 2px 2px 0;
}
}
.order-section-content {
padding: 15px;
}
}
.order-image-item {
margin-bottom: 15px;
.order-image-label {
color: #666;
font-size: 14px;
margin-bottom: 8px;
}
.order-image-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
.order-image {
width: 100px;
height: 100px;
border-radius: 4px;
object-fit: cover;
}
}
}
.pet-record {
margin-bottom: 20px;
.pet-name {
color: #333;
font-size: 15px;
font-weight: 500;
margin-bottom: 10px;
}
.pet-images {
display: flex;
flex-wrap: wrap;
gap: 10px;
.pet-image-container {
.pet-image {
width: 100px;
height: 100px;
border-radius: 4px;
object-fit: cover;
}
}
}
}
.service-record {
margin-bottom: 20px;
.service-name {
color: #333;
font-size: 15px;
font-weight: 500;
margin-bottom: 10px;
}
.service-comparison {
.service-before-after {
display: flex;
gap: 15px;
.service-image-container {
position: relative;
.service-image {
width: 120px;
height: 120px;
border-radius: 4px;
object-fit: cover;
}
.service-image-label {
position: absolute;
bottom: 0;
left: 0;
background-color: rgba(0,0,0,0.5);
color: #fff;
padding: 2px 8px;
font-size: 12px;
border-radius: 0 0 0 4px;
}
}
}
}
}
.custom-service {
.custom-service-item {
margin-bottom: 15px;
.custom-service-name {
color: #333;
font-size: 15px;
font-weight: 500;
margin-bottom: 10px;
}
.custom-service-images {
display: flex;
flex-wrap: wrap;
gap: 10px;
.custom-service-image {
width: 100px;
height: 100px;
border-radius: 4px;
object-fit: cover;
}
}
}
}
</style>

+ 367
- 0
pages/personalCenter/taskDetail.vue View File

@ -0,0 +1,367 @@
<template>
<view class="task-detail">
<!-- 任务头部信息 -->
<view class="task-header">
<view class="task-title">{{taskInfo.title}}</view>
<view class="task-deadline">请于{{taskInfo.deadline}}之前上传任务超时将自动取消</view>
</view>
<!-- 任务进度 -->
<view class="task-progress">
<view class="progress-title">任务进度</view>
<uni-steps :options="stepsList" :active="currentStep" active-icon="checkbox-filled" active-color="#ffaa48"></uni-steps>
</view>
<!-- 任务说明 -->
<view class="task-instruction">
<view class="instruction-header">
<image class="instruction-icon" src="https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/point.png"></image>
<view class="instruction-title">悬赏任务说明</view>
</view>
<view class="instruction-content">
<view class="instruction-main">
<text>请以"{{taskInfo.description}}"为主题编辑小红书宣传笔记~</text>
</view>
<view class="requirement-section">
<view class="requirement-title">封面和配图要求</view>
<view class="requirement-content">
<text>封面: 个人宠物图片</text>
<text>配图: 个人宠物图片或小程序页面截图(共2-3)</text>
</view>
</view>
<view class="requirement-section">
<view class="requirement-title">标题示例 (实际发布可以参照以下)</view>
<view class="title-examples">
<view class="example-item">
<view class="example-tag">H1</view>
<view class="example-text">春节回家猫咪/狗狗怎么办?</view>
</view>
<view class="example-item">
<view class="example-tag">H2</view>
<view class="example-text">今年过年不用担心宠物没人照顾啦~</view>
</view>
<view class="example-item">
<view class="example-tag">H3</view>
<view class="example-text">关于宠物喂养平台的真实体验</view>
</view>
<view class="example-item">
<view class="example-tag">H4</view>
<view class="example-text">出门旅游再也不用担心宠物啦</view>
</view>
</view>
</view>
<view class="requirement-section">
<view class="requirement-title">其他要求</view>
<view class="other-requirements">
<view class="requirement-item">
<view class="requirement-tag"></view>
<view class="requirement-text">文案内容不少于400字</view>
</view>
<view class="requirement-item">
<view class="requirement-tag"></view>
<view class="requirement-text">提及关键词上门猫咪/上门狗狗/上门喂养</view>
</view>
<view class="requirement-item">
<view class="requirement-tag"></view>
<view class="requirement-text">提及猫狗宠物平台并适当好评</view>
</view>
<view class="requirement-item">
<view class="requirement-tag"></view>
<view class="requirement-text">评论区@猫狗宠物官方账号</view>
</view>
</view>
</view>
<view class="note-text">
<text>注意是评论区请务必在正文@官方账号</text>
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="footer-buttons">
<u-button shape="circle" plain @click="cancelTask">取消</u-button>
<u-button shape="circle" color="#ffaa48" @click="reuploadTask">重新上传</u-button>
</view>
</view>
</template>
<script>
import {
getTaskDetail
} from "@/api/system/task.js"
export default {
data() {
return {
taskInfo: {
id: 0,
title: '发布小红书宣传笔记',
description: '主题: 猫狗狗食使用感受&体验',
deadline: '2025-03-28',
taskType: '悬赏任务',
reward: '2',
status: 'PENDING'
},
stepsList: [
{
title: '接受任务'
},
{
title: '上传任务'
},
{
title: '平台审核'
},
{
title: '酬劳到账'
}
],
currentStep: 0
}
},
onLoad(options) {
if (options.id) {
this.loadTaskDetail(options.id);
}
},
methods: {
loadTaskDetail(taskId) {
//
/*
getTaskDetail(taskId).then(res => {
if (res && res.code === 200) {
this.taskInfo = res.data;
//
switch(this.taskInfo.status) {
case 'PENDING':
this.currentStep = 0;
break;
case 'ACCEPTED':
this.currentStep = 1;
break;
case 'SUBMITTED':
this.currentStep = 2;
break;
case 'COMPLETED':
this.currentStep = 3;
break;
}
}
});
*/
//
console.log('加载任务详情,ID:', taskId);
// 1
this.currentStep = 1;
},
cancelTask() {
uni.showToast({
title: '已取消任务',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
},
reuploadTask() {
uni.showToast({
title: '准备重新上传',
icon: 'none'
});
//
}
}
}
</script>
<style lang="scss">
.task-detail {
background-color: #f5f5f7;
min-height: 100vh;
padding-bottom: 120rpx;
.task-header {
background-color: #FFFFFF;
padding: 30rpx;
.task-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.task-deadline {
font-size: 24rpx;
color: #999;
}
}
.task-progress {
background-color: #FFFFFF;
margin-top: 20rpx;
padding: 30rpx;
.progress-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
}
}
.task-instruction {
background-color: #FFFFFF;
margin-top: 20rpx;
padding: 30rpx;
.instruction-header {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.instruction-icon {
width: 60rpx;
height: 60rpx;
margin-right: 20rpx;
}
.instruction-title {
font-size: 32rpx;
font-weight: bold;
color: #A94F20;
}
}
.instruction-content {
.instruction-main {
padding: 20rpx 0;
border-bottom: 1px solid #EEEEEE;
margin-bottom: 20rpx;
text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
}
.requirement-section {
margin-bottom: 30rpx;
.requirement-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
}
.requirement-content {
padding: 20rpx;
background-color: #FFF4E5;
border-radius: 10rpx;
text {
display: block;
font-size: 26rpx;
color: #A94F20;
line-height: 1.8;
}
}
.title-examples {
background-color: #FFF4E5;
padding: 20rpx;
border-radius: 10rpx;
.example-item {
display: flex;
align-items: center;
margin-bottom: 15rpx;
.example-tag {
width: 50rpx;
height: 50rpx;
background-color: #ffaa48;
color: #FFFFFF;
border-radius: 25rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 24rpx;
margin-right: 20rpx;
}
.example-text {
font-size: 26rpx;
color: #A94F20;
}
}
}
.other-requirements {
background-color: #FFF4E5;
padding: 20rpx;
border-radius: 10rpx;
.requirement-item {
display: flex;
align-items: center;
margin-bottom: 15rpx;
.requirement-tag {
width: 40rpx;
height: 40rpx;
color: #A94F20;
display: flex;
justify-content: center;
align-items: center;
font-size: 24rpx;
margin-right: 20rpx;
}
.requirement-text {
font-size: 26rpx;
color: #A94F20;
}
}
}
}
.note-text {
text-align: center;
margin-top: 20rpx;
text {
font-size: 24rpx;
color: #FF5722;
}
}
}
}
.footer-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #FFFFFF;
padding: 20rpx 30rpx;
display: flex;
justify-content: space-around;
align-items: center;
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);
.u-button {
width: 300rpx;
height: 80rpx;
font-size: 28rpx;
}
}
}
</style>

+ 298
- 0
pages/personalCenter/taskList.vue View File

@ -0,0 +1,298 @@
<template>
<view class="task-center">
<u-subsection :list="tabList"
active-color="#ffaa48"
bg-color="#fff"
inactive-color="#aaaaaa"
font-size="16"
:current="curNow"
@change="sectionChange"></u-subsection>
<!-- <view class="container-tabs">
<up-tabs :list="tabList" lineWidth="68rpx" :activeStyle="{
color: '#ffaa48',
fontWeight: 'bold',
transform: 'scale(1.05)'
}" :inactiveStyle="{
color: '#555',
transform: 'scale(1)'
}" :itemStyle="{height:'88rpx',padding:'0 52rpx'}" lineColor="#ffaa48" @click="sectionChange"></up-tabs>
</view> -->
<view v-for="(item,index) in pendingTasks" style="padding:28rpx 36rpx 0;" :key="index">
<view class="task-card">
<view class="task-header">
<view class="task-image">
<image src="" mode="heightFix"></image>
<image src="" mode="heightFix"></image>
</view>
<view class="task-type">{{item.taskType}}</view>
<view class="task-reward">酬劳 <text> ¥{{item.reward}}</text> </view>
</view>
<view class="task-content">
<view class="task-icon">
<image :src="item.icon" style="width: 120rpx; height: 120rpx;" mode="aspectFill"></image>
</view>
<view class="task-info">
<view class="task-title">{{item.title}}</view>
<view class="task-desc">{{item.description}}</view>
<view class="task-deadline">任务截止日期: {{item.deadline}}</view>
</view>
</view>
<view class="task-footer" v-if="true">
<u-button shape="circle" plain text="查看详情" @click="viewTaskDetail(item)"></u-button>
<u-button shape="circle" color="#ffaa48" text="立即上传" @click="uploadTask(item)"></u-button>
</view>
<view class="task-footer" v-else>
<u-button shape="circle" plain text="查看详情" @click="viewTaskDetail(item)"></u-button>
<view class="task-status">已接受</view>
</view>
</view>
</view>
<view v-if="pendingTasks.length === 0" class="empty-tip">
<text>暂无待接受任务</text>
</view>
</view>
</template>
<script>
import {
getTaskList,
acceptTask,
submitTask,
getTaskDetail
} from "@/api/system/task.js"
export default {
data() {
return {
tabList: [
{
name: '待接受',
badge: {
value: 1,
}
},
{
name: '已接受',
badge: {
value: 0,
}
},
],
curNow: 0,
pendingTasks: [{
id: 1,
taskType: '悬赏任务',
reward: '2',
icon: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/point.png',
title: '发布小红书宣传笔记',
description: '主题: 猫狗狗食使用感受&体验',
deadline: '2025-03-28'
},
{
id: 2,
taskType: '悬赏任务',
reward: '2',
icon: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/point.png',
title: '发布小红书宣传笔记',
description: '主题: 猫狗狗食使用感受&体验',
deadline: '2025-03-28'
},
{
id: 3,
taskType: '悬赏任务',
reward: '2',
icon: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/point.png',
title: '发布小红书宣传笔记',
description: '主题: 猫狗狗食使用感受&体验',
deadline: '2025-03-28'
}
],
acceptedTasks: []
}
},
onLoad() {
//
this.getTaskList()
},
methods: {
sectionChange(index) {
this.curNow = index;
},
getTaskList() {
// API
// API使
//
/*
getTaskList().then(res=>{
if (res && res.code === 200) {
let rows = res.rows || []
console.log(rows)
this.pendingTasks = rows.filter(item=>item.status=="PENDING")
this.acceptedTasks = rows.filter(item=>item.status=="ACCEPTED")
}
})
*/
// 使
console.log('加载任务列表')
},
viewTaskDetail(task) {
//
uni.navigateTo({
url: `/pages/personalCenter/taskDetail?id=${task.id}`
});
},
uploadTask(task) {
//
uni.showToast({
title: '上传任务: ' + task.title,
icon: 'none'
});
//
/*
submitTask({
taskId: task.id,
content: '任务提交内容'
}).then(res => {
if (res && res.code === 200) {
uni.showToast({
title: '任务提交成功',
icon: 'success'
});
//
this.getTaskList();
}
});
*/
//
const index = this.pendingTasks.findIndex(item => item.id === task.id);
if (index !== -1) {
const acceptedTask = {
...this.pendingTasks[index]
};
this.acceptedTasks.push(acceptedTask);
this.pendingTasks.splice(index, 1);
}
}
}
}
</script>
<style lang="scss">
.task-center {
background-color: #f5f5f7;
min-height: 100vh;
.task-card {
background-color: #FFFFFF;
border-radius: 16rpx;
margin-bottom: 30rpx;
overflow: hidden;
.task-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
background-color: #FFF4E599;
padding: 15rpx;
.task-image {
margin-right: 30rpx;
display: flex;
align-items: center;
image {
height: 50rpx;
width: 50rpx;
}
}
.task-type {
color: #A94F20;
font-size: 26rpx;
background-color: #FFF4E5;
padding: 10rpx 30rpx;
border-radius: 30rpx;
}
.task-reward {
font-size: 26rpx;
margin-left: auto;
text {
color: #FF5722;
font-weight: bold;
margin-left: 10rpx;
font-size: 30rpx;
}
}
}
.task-content {
display: flex;
margin-bottom: 30rpx;
padding: 0 30rpx 0 30rpx;
.task-icon {
margin-right: 30rpx;
}
.task-info {
flex: 1;
.task-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.task-desc {
font-size: 28rpx;
color: #666;
margin-bottom: 10rpx;
}
.task-deadline {
font-size: 24rpx;
color: #999;
}
}
}
.task-footer {
padding-bottom: 30rpx;
display: flex;
justify-content: center;
align-items: flex-end;
gap: 20rpx;
.u-button {
width: 250rpx;
height: 60rpx;
font-size: 28rpx;
}
.task-status {
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
width: 250rpx;
color: #ffaa48;
font-size: 28rpx;
}
}
}
.empty-tip {
text-align: center;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
}
}
</style>

+ 261
- 0
pages/personalCenter/userInfo.vue View File

@ -0,0 +1,261 @@
<template>
<view class="user-info-container container">
<view class="user-avatar-section">
<view class="user-info-title">
用户头像
</view>
<view style="display: flex;justify-content: center;">
<u-upload
accept="image"
:capture="['album','camera']"
:fileList="fileList"
@afterRead="afterRead"
@delete="deletePic"
:max-count="1"
name="avatar"
width="80"
height="80"
:custom-style="{flex:0}"
>
<image :src="avatarUrl" style="width: 80px;height: 80px;border-radius: 50%;"></image>
</u-upload>
</view>
</view>
<view class="user-info-section">
<view class="user-info-title">
基本信息
</view>
<view class="user-info-form">
<view class="user-info-item">
<view class="user-info-label">昵称</view>
<view class="user-info-input">
<u-input v-model="nickname" placeholder="请输入昵称" :border="false" />
</view>
</view>
<!-- <view class="user-info-item">
<view class="user-info-label">会员等级</view>
<view class="user-info-value">
<text>{{userLevel}}</text>
</view>
</view> -->
</view>
</view>
<view class="user-info-btns">
<view class="user-info-btn" @click="save">
<u-button color="#FFBF60" :loading="loading">
<view style="color: #fff;">
保存
</view>
</u-button>
</view>
</view>
</view>
</template>
<script>
import {updateUserProfile, uploadAvatar} from '@/api/system/user'
import {getPersonalInfo} from "@/api/system/personal.js"
export default {
data() {
return {
loading: false,
fileList: [],
avatarUrl: 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/avatar_1.png',
nickname: '',
userLevel: ''
}
},
onLoad() {
this.getUserInfo()
},
methods: {
//
getUserInfo() {
getPersonalInfo().then(res => {
if (res && (res.id || res.id === 0)) {
this.nickname = res.nickname || ''
this.userLevel = res.level || ''
if (res.avatar) {
this.avatarUrl = res.avatar
this.fileList = [{url: res.avatar}]
}
}
})
},
//
deletePic(event) {
this.fileList.splice(event.index, 1)
this.avatarUrl = 'https://catmdogf.oss-cn-shanghai.aliyuncs.com/CMDF/front/personal/index/avatar_1.png'
},
//
async afterRead(event) {
// multiple true , file
let lists = [].concat(event.file)
let fileListLen = this.fileList.length
lists.map((item) => {
this.fileList.push({
...item,
status: 'uploading',
message: '上传中'
})
})
for (let i = 0; i < lists.length; i++) {
const result = await this.uploadFilePromise(lists[i].url)
let item = this.fileList[fileListLen]
this.fileList.splice(fileListLen, 1, Object.assign(item, {
status: 'success',
message: '',
url: result
}))
this.avatarUrl = result
fileListLen++
}
},
uploadFilePromise(url) {
return new Promise((resolve, reject) => {
let a = uni.uploadFile({
url: 'https://store-test.catmdogd.com/test-api/h5/oss/upload',
filePath: url,
name: 'file',
formData: {
user: 'test'
},
success: (res) => {
setTimeout(() => {
if(res && res.data){
let resData = JSON.parse(res.data);
resolve(resData.url);
}
reject("上传失败");
}, 1000)
}
});
})
},
//
save() {
if (!this.nickname) {
this.$modal.showToast('请输入昵称!')
return
}
this.loading = true
let params = {
nickname: this.nickname,
avatar: this.avatarUrl
}
updateUserProfile(params).then(res => {
if (res && res.code == 200) {
uni.showToast({
title: '保存成功',
duration: 3000,
icon: "none"
})
setTimeout(() => {
this.loading = false
let len = getCurrentPages().length
if (len >= 2) {
uni.navigateBack()
} else {
uni.redirectTo({url: '/pages/personalCenter/index'})
}
}, 1000)
} else {
this.loading = false
uni.showToast({
title: '更新用户信息失败',
duration: 3000,
icon: "none"
})
}
}).catch(() => {
this.loading = false
uni.showToast({
title: '更新用户信息失败',
duration: 3000,
icon: "none"
})
})
}
}
}
</script>
<style lang="scss">
.user-info-container {
position: relative;
height: 100%;
padding-bottom: 90px;
.user-avatar-section {
width: 100%;
background-color: #fff;
padding: 15px 20px;
margin-bottom: 10px;
}
.user-info-section {
width: 100%;
background-color: #fff;
padding: 15px 20px;
}
.user-info-title {
font-size: 14px;
color: #333;
font-weight: bold;
padding-bottom: 15px;
}
.user-info-form {
width: 100%;
}
.user-info-item {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #efefef;
&:last-child {
border-bottom: none;
}
}
.user-info-label {
width: 80px;
color: #333;
font-size: 14px;
}
.user-info-input {
flex: 1;
}
.user-info-value {
flex: 1;
color: #666;
font-size: 14px;
}
.user-info-btns {
background-color: #FFFFFF;
padding: 10px 20px 40px;
width: 100%;
height: 90px;
position: fixed;
bottom: 0;
z-index: 100;
text-align: center;
.user-info-btn {
width: 80%;
margin: 0 auto;
}
}
}
</style>

+ 28
- 5
store/index.js View File

@ -3,13 +3,36 @@ import Vuex from 'vuex'
import user from '@/store/modules/user'
import getters from './getters'
import { getConfigList } from '@/api/system/configList'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
},
getters
state : {
configList : {},
},
mutations : {
// 初始化配置
initConfig(state) {
getConfigList().then(res => {
const configList = {
...state.configList,
}
if (res.code == 200) {
res.result.forEach(n => {
configList[n.keyName] = n.keyContent;
configList[n.keyName + '_keyValue'] = n.keyValue;
});
}
state.configList = configList
uni.$emit('initConfig', state.configList)
})
},
},
modules: {
user
},
getters
})
export default store
export default store

+ 103
- 85
store/modules/user.js View File

@ -1,98 +1,116 @@
import config from '@/config'
import storage from '@/utils/storage'
import constant from '@/utils/constant'
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import {
login,
logout,
getInfo
} from '@/api/login'
import {
getToken,
setToken,
removeToken
} from '@/utils/auth'
const baseUrl = config.baseUrl
const user = {
state: {
token: getToken(),
name: storage.get(constant.name),
avatar: storage.get(constant.avatar),
roles: storage.get(constant.roles),
permissions: storage.get(constant.permissions)
},
state: {
token: getToken(),
name: storage.get(constant.name),
avatar: storage.get(constant.avatar),
roles: storage.get(constant.roles),
permissions: storage.get(constant.permissions)
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
storage.set(constant.name, name)
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
storage.set(constant.avatar, avatar)
},
SET_ROLES: (state, roles) => {
state.roles = roles
storage.set(constant.roles, roles)
},
SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions
storage.set(constant.permissions, permissions)
}
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
storage.set(constant.name, name)
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
storage.set(constant.avatar, avatar)
},
SET_ROLES: (state, roles) => {
state.roles = roles
storage.set(constant.roles, roles)
},
SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions
storage.set(constant.permissions, permissions)
}
},
actions: {
// 登录
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
setToken(res.token)
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
actions: {
// 登录
Login({
commit
}, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
setToken(res.token)
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo().then(res => {
const user = res.user
const avatar = (user == null || user.avatar == "" || user.avatar == null) ? require("@/static/images/profile.jpg") : baseUrl + user.avatar
const username = (user == null || user.userName == "" || user.userName == null) ? "" : user.userName
if (res.roles && res.roles.length > 0) {
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)
} else {
commit('SET_ROLES', ['ROLE_DEFAULT'])
}
commit('SET_NAME', username)
commit('SET_AVATAR', avatar)
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetInfo({
commit,
state
}) {
return new Promise((resolve, reject) => {
getInfo().then(res => {
const user = res.user
const avatar = (user == null || user.avatar == "" || user.avatar == null) ?
require("@/static/images/profile.jpg") : baseUrl + user.avatar
const username = (user == null || user.userName == "" || user.userName ==
null) ? "" : user.userName
if (res.roles && res.roles.length > 0) {
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)
} else {
commit('SET_ROLES', ['ROLE_DEFAULT'])
}
commit('SET_NAME', username)
commit('SET_AVATAR', avatar)
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 退出系统
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
storage.clean()
resolve()
}).catch(error => {
reject(error)
})
})
}
}
// 退出系统
LogOut({
commit,
state
}) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
removeToken()
storage.clean()
resolve()
}).catch(error => {
reject(error)
})
})
}
}
}
export default user
export default user

+ 4
- 1
utils/getUrl.js View File

@ -1,8 +1,11 @@
let current ="develop";
const accountInfo =wx.getAccountInfoSync();
const accountInfo = wx.getAccountInfoSync();
current = accountInfo.miniProgram.envVersion;
const api={
// develop:"http://h5.xzaiyp.top/prod-api",
develop:"https://api-test.catmdogd.com/test-api",
trial:"https://api-test.catmdogd.com/test-api",
release:"https://api.catmdogd.com/prod-api",


Loading…
Cancel
Save