Kaynağa Gözat

退单+动态

lishanshan 7 ay önce
ebeveyn
işleme
3ff0e00ccf

+ 64 - 0
src/api/dynamic.js

@@ -0,0 +1,64 @@
+import request from "@/utils/request.js";
+
+// 发现-动态列表
+export function discoverList(data) {
+    return request({
+        method: 'get',
+        url: '/api/moment/v1/list',
+        data
+    });
+}
+
+// 我的动态列表
+export function myDynamicList(data) {
+    return request({
+        method: 'get',
+        url: '/api/moment/v1/my',
+        data
+    });
+}
+
+// 技师动态列表
+export function jsDynamicList(data) {
+    return request({
+        method: 'get',
+        url: '/api/moment/v1/queryMomentByTechnicianId',
+        data
+    });
+}
+
+// 草稿动态列表
+export function draftDynamicList(data) {
+    return request({
+        method: 'get',
+        url: '/api/moment/v1/drafts',
+        data
+    });
+}
+
+// 发布/保存动态
+export function dynamicPublish(data) {
+    return request({
+        method: 'post',
+        url: '/api/moment/v1/publish',
+        data
+    });
+}
+
+// 编辑动态
+export function dynamicEdit(data) {
+    return request({
+        method: 'post',
+        url: '/api/moment/v1/edit',
+        data
+    });
+}
+
+// 动态详情
+export function dynamicDetail(data) {
+    return request({
+        method: 'get',
+        url: '/api/moment/v1/detail',
+        data
+    });
+}

+ 24 - 0
src/api/index.js

@@ -12,6 +12,30 @@ export function imgUpload(data) {
     });
 }
 
+// 图片上传(多个)
+export function imgsUpload(data) {
+    return request({
+        method: 'post',
+        url: '/common/uploads',
+        data,
+        header: {
+            'content-type': 'multipart/form-data'
+        }
+    });
+}
+
+// 视频上传(单个)
+export function videoUpload(data) {
+    return request({
+        method: 'post',
+        url: '/common/uploadVi',
+        data,
+        header: {
+            'content-type': 'multipart/form-data'
+        }
+    });
+}
+
 // 获取token  
 export function getAccessToken(data) {
     return request({

+ 9 - 0
src/api/staff.js

@@ -61,4 +61,13 @@ export function freeFareAdd(data) {
         url: '/tFareSetting/fareFreeRule/add',
         data
     });
+}
+
+// 禁用免车费规则
+export function freeFareDisable(data) {
+    return request({
+        method: 'post',
+        url: '/tFareSetting/fareFreeRule/disable',
+        data
+    });
 }

+ 52 - 3
src/pages.json

@@ -40,6 +40,32 @@
 				}  
 			}
 		},
+    {
+			"path": "pages/discover/discover",
+			"style": {
+				"navigationBarTitleText": "发现",
+				"navigationBarBackgroundColor": "#2cb8d4",
+				"navigationBarTextStyle": "white",
+				"app-plus": {
+					"softinputNavBar": "none",
+					"titleNView": false
+				}  
+			}
+		},
+    //动态详情
+		{
+			"path": "pages/discover/details",
+			"style": {
+				"navigationBarTitleText": "发现",
+				"navigationBarBackgroundColor": "#2cb8d4",
+				"enablePullDownRefresh":false,
+				"navigationBarTextStyle": "white",
+				"app-plus": {
+					"softinputNavBar": "none",
+					"titleNView": false
+				}  
+			}
+		},
 		{
 			"path": "pages/order/order",
 			"style": {
@@ -112,12 +138,29 @@
 				}  
 			}
 		},
-		{
+    {
+			"path": "pages/my/dynamic/index",
+			"style": {
+				"navigationBarTitleText": "我的动态",
+				"navigationBarBackgroundColor": "#2cb8d4",
+				"navigationBarTextStyle": "white",
+				"app-plus": {
+					"softinputNavBar": "none",
+					"titleNView": false
+				}  
+			}
+		},
+    {
+			"path": "pages/my/dynamic/add_dynamic",
+			"style": {
+				"navigationBarTitleText": "发布动态",
+				"navigationStyle": "custom"
+			}
+		},
+    {
 			"path": "pages/login/wxLogin",
 			"style": {
 				"navigationBarTitleText": "",
-				// "navigationBarBackgroundColor": "#2cb8d4",
-				// "navigationBarTextStyle": "white",
 				"app-plus": {
 					"softinputNavBar": "none",
 					"titleNView": false
@@ -410,6 +453,12 @@
 				"iconPath": "static/identify/people.png",
 				"selectedIconPath": "static/identify/people-cur.png"
 			},
+      {
+				"text": "发现",
+				"pagePath": "pages/discover/discover",
+				"iconPath": "static/discover/discover.png",
+				"selectedIconPath": "static/discover/discover-cur.png"
+			},
 			{
 				"text": "订单",
 				"pagePath": "pages/order/order",

+ 289 - 0
src/pages/discover/details.vue

@@ -0,0 +1,289 @@
+<template>
+  <view class="page-container">
+    <view class="detail-content">
+      <view class="detail-title">
+        <view class="info">
+          <view class="service-avatar">
+            <image
+              :src="this.public + details.technicianAvatar"
+              mode="aspectFill"
+            ></image>
+          </view>
+          <text class="name">{{ details.technicianNickName }}</text>
+        </view>
+        <view class="status">
+          <view
+            :class="{
+              'service-status': details.technicianStatus != 2,
+              'reserve-status': details.technicianStatus == 2,
+            }"
+            >{{ details.technicianStatus === 2 ? '可预约' : '可服务' }}</view
+          >
+        </view>
+      </view>
+      <view class="swiper-box">
+        <text class="tips">图片仅供参考</text>
+        <MySwiper :list="swipeList" :autoplay="autoplay" />
+      </view>
+      <view class="detail-info">
+        <view class="title">{{ details.title }}</view>
+        <view class="describe">{{ details.content }}</view>
+        <view class="date">{{ details.publishTime }}</view>
+      </view>
+      <view class="location">
+        <view class="free_box">
+          <image
+            src="/static/discover/icon_location.png"
+            mode=""
+            class="location-icon"
+          />
+          {{ details.distance }}km
+          <image
+            src="/static/discover/icon_eye_cru.png"
+            mode=""
+            class="eye-icon"
+          />
+          {{ details.viewCount }}
+        </view>
+        <view class="btn" @click.stop="nowYuyue(details)">
+          <text>预约TA</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+<script>
+import { dynamicDetail } from '@/api/dynamic.js'
+import MySwiper from '@/components/swiper/index.vue'
+
+export default {
+  components: { MySwiper },
+  data() {
+    return {
+      public: this.$globalData.publicUrl, //图片前缀
+      details: '',
+      autoplay: false,
+      swiper: [],
+      swipeList: [],
+    }
+  },
+  onLoad(options) {
+    this.getDetails(options.momentId)
+  },
+  methods: {
+    getDetails(id) {
+      this.swiper = []
+      const params = {
+        momentId: id,
+        latitude: uni.getStorageSync('latitude'),
+        longitude: uni.getStorageSync('longitude'),
+      }
+      dynamicDetail(params).then(res => {
+        if (res.data.code === 200 && res.data.data) {
+          // res.data.data.mediaType = 2
+          // res.data.data.mediaList = [
+          //   {
+          //     mediaType: 2,
+          //     mediaUrl:
+          //       'https://test.baoxianzhanggui.com/nightFragrance/profile/upload/2025/05/23/WeChat_20250523101737_20250523151440A001.mp4',
+          //   },
+          // ]
+          if (res.data.data.mediaList && res.data.data.mediaList.length > 0) {
+            res.data.data.mediaList.forEach(item => {
+              this.swiper.push(item)
+            })
+          }
+          if (this.swiper) {
+            this.swipeList = this.swiper.map(item => {
+              const isVideo = res.data.data.mediaType == 2
+              return {
+                src: item.mediaUrl,
+                type: isVideo ? 'video' : 'image',
+                // 视频封面(需替换)
+                poster: isVideo
+                  ? res.data.data.coverUrl
+                  : res.data.data.mediaList[0].mediaUrl,
+              }
+            })
+          }
+          let data = {
+            ...res.data.data,
+            publishTime: res.data.data.publishTime
+              .replace('T', ' ')
+              .substring(0, 16),
+          }
+          this.details = data
+        }
+      })
+    },
+    //立即预约
+    nowYuyue(item) {
+      uni.navigateTo({
+        url: '/pages/identify/details?id=' + item.technicianId,
+      })
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  font-family: PingFang SC, PingFang SC;
+  background-color: #f7ffff;
+}
+
+.detail-content {
+  overflow-y: scroll;
+  background-color: #f7ffff;
+
+  .detail-title {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 24rpx 32rpx 24rpx 34rpx;
+
+    .info {
+      display: flex;
+      align-items: center;
+
+      .service-avatar {
+        width: 64rpx;
+        height: 64rpx;
+        border-radius: 50%;
+        overflow: hidden;
+        margin-right: 16rpx;
+        image {
+          width: 100%;
+          height: 100%;
+          object-fit: cover;
+        }
+      }
+
+      .name {
+        display: block;
+        font-size: 32rpx;
+        color: #333;
+        margin-right: 12rpx;
+      }
+    }
+
+    .status {
+      .service-status {
+        color: #fff;
+        background-color: #03c8be;
+        font-size: 24rpx;
+        font-weight: bold;
+        padding: 0 25rpx;
+        border-radius: 30px;
+        width: 124rpx;
+        height: 46rpx;
+        line-height: 46rpx;
+        margin-top: 4rpx;
+      }
+
+      .reserve-status {
+        color: #a1601f;
+        background-color: #facd9a;
+        font-size: 24rpx;
+        font-weight: bold;
+        padding: 0 25rpx;
+        border-radius: 30px;
+        width: 124rpx;
+        height: 46rpx;
+        line-height: 46rpx;
+        margin-top: 4rpx;
+      }
+    }
+  }
+
+  swiper {
+    height: 516rpx;
+  }
+
+  .swiper-box {
+    width: 100%;
+    height: 516rpx;
+    position: relative;
+
+    .tips {
+      font-size: 18rpx;
+      color: #808080;
+      position: absolute;
+      top: 10px;
+      left: 10px;
+    }
+  }
+
+  .detail-info {
+    padding: 32rpx 24rpx;
+
+    .title {
+      font-weight: bold;
+      font-size: 36rpx;
+      color: #333333;
+      margin-bottom: 20rpx;
+    }
+    .describe {
+      font-weight: 500;
+      font-size: 24rpx;
+      color: #333333;
+      line-height: 36rpx;
+      margin-bottom: 20rpx;
+    }
+    .date {
+      font-weight: 500;
+      font-size: 24rpx;
+      color: #999999;
+    }
+  }
+
+  .location {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    height: 92rpx;
+    padding: 28rpx 42rpx;
+    color: #9e9e9e;
+    background-color: #fff;
+
+    .free_box {
+      font-size: 24rpx;
+      color: #999999;
+      display: flex;
+      align-items: center;
+
+      .eye-icon {
+        width: 30rpx;
+        height: 24rpx;
+        margin: 0 10rpx 0 32rpx;
+      }
+
+      .location-icon {
+        width: 20rpx;
+        height: 25rpx;
+        color: #03c8be;
+        margin-right: 10rpx;
+      }
+    }
+
+    .btn {
+      width: 164rpx;
+      height: 56rpx;
+      color: #ffffff;
+      font-size: 30rpx;
+      line-height: 30rpx;
+      font-weight: bold;
+      text-align: center;
+      padding: 14rpx 0;
+      background: linear-gradient(135deg, #ff8b66 0%, #ee5b2d 100%);
+      border-radius: 98rpx;
+    }
+  }
+}
+
+::v-deep {
+  uni-swiper .uni-swiper-dot-active {
+    background-color: #fff;
+  }
+}
+</style>

+ 311 - 0
src/pages/discover/discover.vue

@@ -0,0 +1,311 @@
+<template>
+  <view class="page-container">
+    <z-paging
+      ref="paging"
+      v-model="dynamicList"
+      :paging-style="{
+        background: '#F7FFFF',
+      }"
+      @query="queryList"
+    >
+      <template #top>
+        <view class="tab-box">
+          <view
+            v-for="(item, index) in tabList"
+            :key="index"
+            :class="{ item: true, active: current === index + 1 }"
+            @click="onTabChange(index)"
+          >
+            {{ item.name }}
+          </view>
+        </view>
+      </template>
+      <view class="list-box" v-if="dynamicList && dynamicList.length > 0">
+        <view
+          class="item"
+          v-for="(item, index) in dynamicList"
+          :key="index"
+          @click="goDetails(item)"
+        >
+          <view class="item-image">
+            <image
+              :src="item.coverUrl || '/static/common/default-avatar.png'"
+              mode="aspectFill"
+            ></image>
+          </view>
+          <view class="item-content">
+            <view class="item-title">
+              <text>{{ item.title }}</text>
+              <view
+                :class="{
+                  'service-status': item.technicianStatus != 2,
+                  'reserve-status': item.technicianStatus == 2,
+                }"
+                >{{ item.technicianStatus === 2 ? '可预约' : '可服务' }}</view
+              >
+            </view>
+            <view class="item-service">
+              <view class="service-avatar">
+                <image
+                  :src="$globalData.publicUrl + item.technicianAvatar"
+                  mode="aspectFill"
+                ></image>
+              </view>
+              <view class="service-info">
+                <text class="service-name">{{ item.technicianNickName }}</text>
+              </view>
+              <view class="diamond-count">
+                <image
+                  src="/static/discover/icon_eye.png"
+                  mode=""
+                  class="eye-icon"
+                />
+                <text>{{ item.viewCount }}</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <template v-slot:empty>
+        <u-empty
+          :text="!isLogin ? '您还没有登录' : '您还没有相关动态呦~'"
+          icon="/static/common/logo1.png"
+        >
+          <u-button
+            size="small"
+            text="去登录"
+            color="linear-gradient(to right, #35c99d, #27aea7)"
+            :customStyle="{ marginTop: '20rpx' }"
+            v-if="!isLogin"
+            @click="goLogin"
+          >
+          </u-button>
+        </u-empty>
+      </template>
+    </z-paging>
+  </view>
+</template>
+
+<script>
+import { discoverList } from '@/api/dynamic.js'
+
+export default {
+  data() {
+    return {
+      dynamicList: [],
+      current: 1,
+      tabList: [
+        {
+          name: '推荐',
+          value: 1,
+        },
+        {
+          name: '同城商户',
+          value: 2,
+        },
+        {
+          name: '附近',
+          value: 3,
+        },
+      ],
+      isLogin: false,
+    }
+  },
+  onLoad() {
+    this.isLogin = !!uni.getStorageSync('wx_copenid')
+  },
+  methods: {
+    queryList(pageNo, pageSize) {
+      let params = {
+        pageNum: pageNo,
+        pageSize: pageSize,
+        queryType: this.current,
+        cityCode: uni.getStorageSync('areaCode'),
+        longitude: uni.getStorageSync('longitude'),
+        latitude: uni.getStorageSync('latitude'),
+      }
+      discoverList(params).then(res => {
+        if (res.data.code === 200 && res.data.data) {
+          let data = res.data.data
+          data.forEach(item => {
+            item.publishTime = item.publishTime
+              .replace('T', ' ')
+              .substring(0, 16)
+          })
+          this.$refs.paging.complete(data)
+        } else {
+          this.$refs.paging.complete([])
+        }
+      })
+    },
+    onTabChange(index) {
+      this.current = index + 1
+      this.$refs.paging.reload()
+    },
+    // 动态详情
+    goDetails(item) {
+      let str = uni.$u.queryParams({
+        momentId: item.id,
+      })
+      uni.navigateTo({
+        url: `/pages/discover/details${str}`,
+      })
+    },
+    goLogin() {
+      uni.navigateTo({
+        url: '/pages/login/wxLogin',
+      })
+    },
+  },
+}
+</script>
+
+<style scoped lang="scss">
+.page-container {
+  background-color: #f7ffff;
+
+  .tab-box {
+    height: 90rpx;
+    display: flex;
+    justify-content: start;
+    align-items: center;
+    font-size: 28rpx;
+    color: #333333;
+    padding-left: 20rpx;
+    background: linear-gradient(180deg, #c1fff8 0%, #b8fff3 34%, #f5ffff 100%);
+    z-index: 999;
+
+    .item {
+      height: 55%;
+      padding: 0 12rpx;
+      display: flex;
+      align-items: center;
+    }
+
+    .active {
+      font-weight: 500;
+      font-size: 30rpx;
+      color: #03c8be;
+      position: relative;
+
+      &::after {
+        content: '';
+        width: 48rpx;
+        height: 4rpx;
+        position: absolute;
+        bottom: 0;
+        left: 50%;
+        transform: translateX(-50%);
+        background-color: #03c8be;
+      }
+    }
+  }
+
+  .list-box {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: space-between;
+    padding: 26rpx;
+
+    .item {
+      width: calc(50% - 14rpx);
+      margin-bottom: 28rpx;
+      box-sizing: border-box;
+      background: #ffffff;
+      box-shadow: 0rpx 4rpx 12rpx 0rpx rgba(88, 209, 187, 0.1);
+      border-radius: 12rpx;
+      overflow: hidden;
+
+      .item-image {
+        width: 100%;
+        height: 200rpx;
+        background-color: #f0f0f0;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        overflow: hidden;
+
+        image {
+          width: 100%;
+          height: 100%;
+          object-fit: cover;
+        }
+      }
+
+      .item-content {
+        padding: 20rpx;
+
+        .item-title {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          margin-bottom: 16rpx;
+
+          text:first-child {
+            font-size: 28rpx;
+            font-weight: bold;
+            color: #333;
+          }
+
+          .service-status {
+            font-size: 24rpx;
+            color: #03c8be;
+          }
+
+          .reserve-status {
+            font-size: 24rpx;
+            color: #f16336;
+          }
+        }
+
+        .item-service {
+          display: flex;
+
+          .service-avatar {
+            width: 36rpx;
+            height: 36rpx;
+            border-radius: 50%;
+            overflow: hidden;
+            margin-right: 12rpx;
+
+            image {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+          }
+
+          .service-info {
+            display: flex;
+            flex: 1;
+
+            .service-name {
+              display: block;
+              font-size: 24rpx;
+              color: #333;
+              margin-right: 12rpx;
+            }
+          }
+
+          .diamond-count {
+            display: flex;
+            align-items: center;
+
+            image {
+              width: 22rpx;
+              height: 18rpx;
+              margin-right: 8rpx;
+            }
+
+            text {
+              font-size: 24rpx;
+              color: #999999;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 31 - 2
src/pages/identify/details.vue

@@ -38,6 +38,7 @@
 		<view class="server-content">
 			<view class="tab">
 				<text class="tips" :class=" checkedIndex === 0 ? 'active' : '' " @click="switchTab(0)">服务内容</text>
+				<text class="tips" :class=" checkedIndex === 2 ? 'active' : '' " @click="switchTab(2)">技师动态</text>
 				<text class="tips" :class=" checkedIndex === 1 ? 'active' : '' " @click="switchTab(1)">顾客评价</text>
 			</view>
 			<view class="tab_content">
@@ -112,6 +113,13 @@
 						</view>
 					</view>
 				</view>
+				<view class="tab-pane" :class="checkedIndex === 2 ? 'active' : ''">
+          <!-- 技师动态列表 -->
+					<MyDynamicList :data-list="dynamicList" type="merchant" />
+					<!-- 空状态 -->
+          <view v-if="dynamicList < 1" style="text-align: center;padding:50rpx 0;font-size: 28rpx;color: #ccc;">暂无动态
+					</view>
+        </view>
 			</view>
 		</view>
 		<view class="reserved">
@@ -127,9 +135,11 @@ import {
 	addCollect,
 	cancleCollect
 } from '@/api/index.js';
+import { jsDynamicList } from '@/api/dynamic.js'
 import MySwiper from '@/components/swiper/index.vue';
+import MyDynamicList from '@/pages/my/dynamic/components/my_list.vue';
 export default {
-    components: { MySwiper },
+    components: { MySwiper, MyDynamicList },
 	data() {
 		return {
 			details: '',
@@ -141,11 +151,14 @@ export default {
 			comList: [],
 			public: this.$globalData.publicUrl, //图片前缀
 			swiper: [],
-			swipeList: []
+			swipeList: [],
+      dynamicList: [] // 技师动态列表
 		}
 	},
 	onLoad(options) {
 		this.getDetails(options.id)
+    // 技师动态列表
+		this.getJsDynamicList(options.id)
 	},
 	methods: {
 		switchTab(index) {
@@ -155,6 +168,22 @@ export default {
 				this.checkedIndex = index
 			}
 		},
+		getJsDynamicList(id) {
+			let params = {
+				technicianId: id,
+			}
+			jsDynamicList(params).then(res => {
+				if (res.data.code === 200 && res.data.data.records) {
+					let data = res.data.data.records
+					data.forEach(item => {
+						item.publishTime = item.publishTime
+							.replace('T', ' ')
+							.substring(0, 16)
+					})
+					this.dynamicList = data
+				}
+			})
+		},
 		//查看认证信息
 		goApprove(id) {
 			uni.navigateTo({

+ 16 - 6
src/pages/identify/pay_order.vue

@@ -124,6 +124,7 @@
 				</view>
 			</view>
 			<view class="btn" v-else>
+        <view>{{ remainingTime }}</view>
 				<view class="">
 					已过期
 				</view>
@@ -252,7 +253,7 @@ export default {
 					this.dTotalMoney = this.details.dTotalMoney;
 					this.priceDifference = this.details.priceDifference;
 					// 支付倒计时
-					this.remainingTime = res.data.data.records[0].remainingTime;
+          this.$set(this, 'remainingTime', res.data.data.records[0].remainingTime * 1000);          
 					// 获取部门车费
 					this.fareSetting(res.data.data.records[0].deptId);
 					// 获取服务时间
@@ -275,9 +276,11 @@ export default {
 				enable: 1
 			}
 			getFareSetting(params).then(res => {
-				this.baseFare = res.data.data.records[0].baseFare
-				this.baseDistance = res.data.data.records[0].baseDistance
-				this.additionalFarePer = res.data.data.records[0].additionalFarePer
+        if(res.data.data.records && res.data.data.records.length > 0){
+				  this.baseFare = res.data.data.records[0].baseFare || ''
+				  this.baseDistance = res.data.data.records[0].baseDistance
+				  this.additionalFarePer = res.data.data.records[0].additionalFarePer
+        }
 			})
 		},
 		// 服务时间
@@ -908,8 +911,11 @@ export default {
 			flex-direction: column;
 
 			.remaining_time {
-				height: 30rpx;
-				margin-top: 10rpx;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+				height: 35rpx;
+				margin-top: 5rpx;
 				font-size: 24rpx;
 
 				::v-deep.u-countdown-time {
@@ -919,4 +925,8 @@ export default {
 		}
 	}
 }
+
+::v-deep .u-count-down {
+  margin-left: 10rpx !important;
+}
 </style>

+ 516 - 0
src/pages/my/dynamic/add_dynamic.vue

@@ -0,0 +1,516 @@
+<template>
+  <view class="publish-container">
+    <u-navbar title="发布动态" bgColor="#fff" @leftClick="cancel"></u-navbar>
+
+    <view class="content-body" :style="{ marginTop: '44px' }">
+      <view class="text-area">
+        <u--textarea
+          v-model="formData.title"
+          placeholder="请输入标题..."
+          maxLength="20"
+          border="none"
+          height="20"
+        ></u--textarea>
+        <view class="line"></view>
+      </view>
+      <view class="text-area">
+        <u--textarea
+          v-model="formData.content"
+          placeholder="记录美好生活时刻..."
+          maxLength="300"
+          border="none"
+          height="150"
+          count
+        ></u--textarea>
+      </view>
+      <view class="media-area">
+        <view v-if="formData.videoUrl">
+          <uni-icons
+            class="icon iconfont icon-cuo"
+            type="trash-filled"
+            size="20"
+            @tap="delectVideo"
+          ></uni-icons>
+          <video
+            ref="myVideo"
+            :src="formData.videoUrl"
+            class="video-container"
+            controls
+          ></video>
+        </view>
+        <cl-upload
+          v-else
+          v-model="fileList"
+          :action="uploadApi"
+          :headers="headers"
+          :count="maxCount"
+          :listStyle="{
+            columnGap: '20rpx',
+            rowGap: '20rpx',
+            width: '130rpx',
+            radius: '12rpx',
+          }"
+          :imageFormData="{
+            count: 9,
+            size: 2,
+          }"
+          :videoFromData="{
+            count: 1,
+            size: 50,
+          }"
+          :maxVideo="1"
+          :fileType="mediaType"
+          addImg="/static/discover/ic_upload.png"
+          @onSuccess="onSuccess"
+          @onMediaTypeSelect="selectMediaType"
+          @onError="onCancel"
+        ></cl-upload>
+      </view>
+      <view class="tips">
+        <text v-if="fileList.length === 0"
+          >支持上传图片(最多9张)或视频(1个)</text
+        >
+      </view>
+    </view>
+    <view class="location-cell">
+      <u-cell-group :border="false">
+        <u-cell
+          title="发布地"
+          :value="formData.location || '请选择'"
+          isLink
+          :border="true"
+          @click="selectLocation"
+        >
+        </u-cell>
+        <u-cell
+          title="可见范围"
+          :value="formData.visibleRange || '请选择'"
+          isLink
+          :border="true"
+          @click="showScopePicker = true"
+        >
+        </u-cell>
+      </u-cell-group>
+    </view>
+    <view class="footer-bar">
+      <u-button
+        text="立即发布"
+        color="linear-gradient(80deg,#02f6c1 0%,#07cdc4 100%)"
+        shape="circle"
+        customStyle="height: 48px; font-size: 18px;"
+        @click="submit('publish')"
+      ></u-button>
+    </view>
+    <!-- 可见范围 -->
+    <u-picker
+      title="请选择"
+      :show="showScopePicker"
+      :columns="scopeList"
+      confirmColor="#1ecbc3"
+      @confirm="confirmScope"
+      @cancel="showScopePicker = false"
+    ></u-picker>
+    <!-- 确认返回 -->
+    <u-popup :show="popShow" mode="center" :round="12">
+      <view class="popup-content">
+        <view class="popup-title">确认返回</view>
+        <view class="popup-desc">
+          <text>是否存入草稿箱?</text>
+        </view>
+        <view class="popup-btn">
+          <u-button
+            text="不保存"
+            shape="circle"
+            customStyle="width: 225rpx; height: 68rpx; font-size: 28px; color: #1BD6CD; border-color: #1BD6CD;"
+            @click="popClose"
+          ></u-button>
+          <u-button
+            text="保存"
+            color="linear-gradient( 90deg, #1AD7CE 0%, #21C9C1 100%)"
+            shape="circle"
+            customStyle="width: 225rpx; height: 68rpx; font-size: 28px;"
+            @click="save('draft')"
+          ></u-button>
+        </view>
+      </view>
+    </u-popup>
+  </view>
+</template>
+
+<script>
+import { baseUrl } from '@/common/config.js'
+import { dynamicPublish, dynamicEdit, dynamicDetail } from '@/api/dynamic'
+
+export default {
+  data() {
+    return {
+      loading: false,
+      showScopePicker: false,
+      popShow: false,
+      type: '',
+      formData: {
+        title: '',
+        content: '',
+        location: '',
+        address: '',
+        latitude: '',
+        longitude: '',
+        visibleRange: '',
+        videoUrl: '',
+        videoCoverUrl: '',
+        imageUrls: [],
+      },
+      headers: {
+        Authorization: `tf: ${uni.getStorageSync('access-token')}`,
+      },
+      scopeList: [['公开']],
+      fileList: [],
+      mediaType: 'all', // 默认为图片模式,上传第一个文件后确定类型
+      uploadApi: baseUrl + '/common/upload', // 默认使用图片上传接口
+    }
+  },
+  computed: {
+    maxCount() {
+      return this.mediaType === 'video' ? 1 : 9
+    },
+  },
+  onLoad(options) {
+    const { momentId, type } = options
+    this.type = type
+    if (momentId) {
+      this.getDynamicDetail(momentId)
+    }
+  },
+  methods: {
+    // 获取动态详情
+    async getDynamicDetail(id) {
+      this.loading = true
+      try {
+        const params = {
+          momentId: id,
+          latitude: uni.getStorageSync('latitude'),
+          longitude: uni.getStorageSync('longitude'),
+        }
+        const res = await dynamicDetail(params)
+        if (res.data.code === 200) {
+          const data = res.data.data
+          // 回显表单数据
+          const resData = {
+            momentId: data.id,
+            title: data.title || '',
+            content: data.content || '',
+            location: data.location || '',
+            address: data.address || '',
+            latitude: data.latitude || '',
+            longitude: data.longitude || '',
+            visibleRange: data.visibleRange === 1 ? '公开' : '',
+          }
+          this.formData = { ...this.formData, ...resData }
+          // 回显媒体文件
+          if (data.mediaList && data.mediaList.length > 0) {
+            const mediaType = data.mediaList[0].mediaType
+            if (mediaType === 1) {
+              // 图片类型
+              this.mediaType = 'image'
+              this.uploadApi = baseUrl + '/common/uploads'
+              this.fileList = data.mediaList.map(item => item.mediaUrl)
+            } else if (mediaType === 2) {
+              // 视频类型
+              this.mediaType = 'video'
+              this.uploadApi = baseUrl + '/common/uploadVi'
+              this.formData.videoUrl = data.mediaList[0].mediaUrl
+              // this.formData.videoUrl = data.videoUrl
+              this.formData.videoCoverUrl = data.coverUrl
+            }
+          }
+        } else {
+          uni.showToast({ title: res.message || '获取详情失败', icon: 'none' })
+        }
+      } catch (error) {
+        uni.showToast({ title: '网络异常,请稍后重试', icon: 'none' })
+      } finally {
+        this.loading = false
+      }
+    },
+    onSuccess(reslut) {
+      // 把服务端返回的图片地址添加到list中与组件数据同步
+      if (reslut.code === 200) {
+        const isVideo = reslut.url.endsWith('.mp4')
+        if (isVideo) {
+          this.formData.videoUrl = reslut.url
+          console.log('视频地址:', this.formData.videoUrl)
+          this.formData.videoCoverUrl = reslut.coverUrl
+        } else {
+          this.fileList.push(reslut.url)
+        }
+      } else {
+        this.mediaType = 'all'
+      }
+    },
+    // 确定媒体类型
+    selectMediaType(type) {
+      if (type === 'image') {
+        this.uploadApi = baseUrl + '/common/uploads'
+        this.mediaType = 'image'
+      } else if (type === 'video') {
+        this.uploadApi = baseUrl + '/common/uploadVi'
+        this.mediaType = 'video'
+      }
+    },
+    // 处理媒体类型取消事件
+    onCancel() {
+      this.uploadApi = baseUrl + '/common/uploads'
+      this.mediaType = 'all'
+    },
+    delectVideo() {
+      uni.showModal({
+        title: '提示',
+        content: '是否要删除此视频',
+        success: res => {
+          if (res.confirm) {
+            this.formData.videoUrl = ''
+            this.formData.videoCoverUrl = ''
+            // 重置媒体类型
+            this.mediaType = 'all'
+          }
+        },
+      })
+    },
+    // 选择发布地
+    selectLocation() {
+      uni.chooseLocation({
+        success: res => {
+          // console.log('选择的位置信息:', res)
+          const locationData = {
+            location: res.name,
+            address: res.address,
+            latitude: res.latitude,
+            longitude: res.longitude,
+          }
+          this.formData = {
+            ...this.formData,
+            ...locationData,
+          }
+        },
+      })
+    },
+    // 选择可见范围
+    confirmScope(e) {
+      this.formData.visibleRange = e.value[0]
+      this.showScopePicker = false
+    },
+    // 保存草稿
+    cancel() {
+      this.popShow = true
+    },
+    popClose() {
+      this.popShow = false
+      uni.navigateBack()
+    },
+    save() {
+      this.popShow = false
+      this.submit('draft')
+    },
+    async submit(type) {
+      if (
+        type == 'publish' &&
+        (!this.formData.title ||
+          !this.formData.content ||
+          !this.formData.location ||
+          this.fileList.length === 0 ||
+          !this.formData.visibleRange)
+      ) {
+        uni.showToast({ title: '请完善动态内容', icon: 'none' })
+        return
+      } else if (type == 'draft') {
+        if (!this.formData.title) {
+          uni.showToast({ title: '动态标题不能为空', icon: 'none' })
+          return
+        }
+        if (!this.formData.visibleRange) {
+          uni.showToast({ title: '请选择可见范围', icon: 'none' })
+          return
+        }
+      }
+
+      // 检查是否有文件正在上传
+      const uploadingFiles = this.fileList.filter(
+        file => file.status === 'uploading'
+      )
+      if (uploadingFiles.length > 0) {
+        uni.showToast({ title: '文件上传中,请稍候', icon: 'none' })
+        return
+      }
+
+      this.loading = true
+
+      try {
+        // 提交数据
+        const postData = {
+          ...this.formData,
+          isDraft: type == 'draft' ? 1 : 0,
+          mediaType: this.formData.videoUrl ? 2 : 1,
+          imageUrls: this.fileList,
+          visibleRange: this.formData.visibleRange ? 1 : '',
+        }
+
+        console.log('提交数据:', postData)
+
+        // 调用发布接口
+        const api = this.type === 'edit' ? dynamicEdit : dynamicPublish
+        const res = await api(postData)
+
+        // 接口调用成功
+        if (res.data.code == 200) {
+          uni.showToast({
+            title: type === 'publish' ? '发布成功,等待审核' : '保存草稿成功',
+            icon: 'none',
+          })
+
+          setTimeout(() => {
+            uni.navigateBack({
+              delta: 1,
+            })
+          }, 800)
+        } else {
+          uni.showToast({
+            title: res.data.msg,
+            icon: 'none',
+          })
+        }
+      } catch (error) {
+        uni.showToast({
+          title: '网络异常,请稍后重试',
+          icon: 'none',
+        })
+      } finally {
+        this.loading = false
+      }
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.publish-container {
+  height: 100vh;
+  padding: 32rpx 32rpx 150rpx;
+  background: #f8f8f8;
+  box-sizing: border-box;
+  overflow: scroll;
+
+  .content-body {
+    background: #fff;
+    padding: 32rpx;
+    border-radius: 20rpx;
+    margin-bottom: 24rpx;
+  }
+
+  .location-cell {
+    background: #fff;
+    border-radius: 20rpx;
+    padding: 12rpx 2rpx;
+  }
+
+  .footer-bar {
+    background: #f8f8f8;
+    box-shadow: none;
+    padding: 0 32rpx 20rpx;
+  }
+}
+
+.text-area {
+  margin-bottom: 30rpx;
+  .line {
+    border-bottom: 1px solid #e5e5e5;
+    background: #cccccc;
+    border-radius: 4rpx;
+  }
+}
+
+.media-area {
+  margin: 20rpx 0 20rpx;
+  justify-content: space-between;
+  position: relative;
+
+  .icon-cuo {
+    position: absolute;
+    left: 295upx;
+    top: 5upx;
+    color: #ffffff !important;
+    z-index: 999;
+    border-top-right-radius: 20upx;
+    border-bottom-left-radius: 20upx;
+  }
+
+  .video-container {
+    width: 340upx;
+    height: 240upx;
+    border-radius: 12rpx;
+  }
+}
+
+.tips {
+  font-size: 24rpx;
+  color: #999;
+  margin-bottom: 20rpx;
+}
+
+::v-deep {
+  .u-line {
+    border: none !important;
+  }
+  .u-button__text {
+    font-family: PingFang SC, PingFang SC;
+    font-weight: 500;
+    font-size: 32rpx;
+  }
+  .u-upload__wrap__preview {
+    border-radius: 12rpx;
+  }
+  .cl-updata .file-list .add-image {
+    border: none;
+
+    ._image {
+      width: 100%;
+    }
+  }
+}
+
+.popup-content {
+  width: 630rpx;
+  height: 380rpx;
+  .popup-title {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    font-weight: bold;
+    font-size: 32rpx;
+    color: #000000;
+    height: 92rpx;
+    background: #e6f8f7;
+    border-radius: 24rpx 24rpx 0rpx 0rpx;
+  }
+  .popup-desc {
+    height: 168rpx;
+    font-weight: 500;
+    font-size: 28rpx;
+    color: #333333;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+  .popup-btn {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 120rpx;
+    padding: 24rpx 60rpx;
+    border-top: 2rpx solid #eeeeee;
+    ::v-deep .u-botton {
+      width: 225rpx;
+      height: 68rpx;
+    }
+  }
+}
+</style>

+ 137 - 0
src/pages/my/dynamic/components/draft_list.vue

@@ -0,0 +1,137 @@
+<template>
+  <view class="page-container">
+    <view class="list-box" v-if="dataList && dataList.length > 0">
+      <view class="item" v-for="(item, index) in dataList" :key="index">
+        <view class="item-content">
+          <view class="item-title">
+            <text>{{ item.title }}</text>
+            <view class="status" @click="onEdit(item)">
+              <view
+                :class="{
+                  'publish-status': item.auditStatus != 3,
+                  'edit-status': item.auditStatus == 3,
+                }"
+                >{{ item.auditStatus == 3 ? '重新编辑' : '立即发布' }}</view
+              >
+            </view>
+          </view>
+          <u-tooltip :text="item.content" :showCopy="false"></u-tooltip>
+          <view class="item-time">
+            <text>{{ item.publishTime }}</text>
+          </view>
+          <view v-if="item.auditStatus == 3" class="item-tip">
+            <text>{{ item.rejectReason }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'draft-list',
+  props: {
+    dataList: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  methods: {
+    onEdit(item) {
+      let str = uni.$u.queryParams({
+        momentId: item.id,
+        type: 'edit'
+      })
+      uni.navigateTo({
+        url: `/pages/my/dynamic/add_dynamic${str}`,
+      })
+    },
+  },
+}
+</script>
+
+<style scoped lang="scss">
+.list-box {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  padding: 32rpx;
+
+  .item {
+    width: 100%;
+    margin-bottom: 28rpx;
+    box-sizing: border-box;
+    background: #ffffff;
+    box-shadow: 0rpx 4rpx 12rpx 0rpx rgba(88, 209, 187, 0.1);
+    border-radius: 24rpx;
+    overflow: hidden;
+
+    .item-content {
+      padding: 32rpx 28rpx 32rpx;
+      font-weight: 500;
+      font-size: 24rpx;
+
+      .item-title {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 16rpx;
+
+        text:first-child {
+          font-size: 32rpx;
+          color: #333333;
+        }
+
+        .status {
+          .publish-status {
+            color: #fff;
+            background: linear-gradient(80deg, #02f6c1 0%, #07cdc4 100%);
+            font-size: 28rpx;
+            padding: 0 25rpx;
+            border-radius: 98rpx;
+            width: 165rpx;
+            height: 56rpx;
+            line-height: 56rpx;
+            text-align: center;
+          }
+
+          .edit-status {
+            color: #999999;
+            background-color: #fff;
+            font-size: 28rpx;
+            padding: 0 25rpx;
+            border-radius: 98rpx;
+            border: 2rpx solid #999999;
+            width: 165rpx;
+            height: 56rpx;
+            line-height: 50rpx;
+            text-align: center;
+          }
+        }
+      }
+
+      .item-time {
+        display: block;
+        color: #999999;
+        margin-bottom: 16rpx;
+      }
+
+      .item-tip {
+        color: #ff2f2f;
+      }
+    }
+  }
+}
+
+::v-deep {
+  .u-tooltip__wrapper__text {
+    width: 454rpx;
+    font-weight: 500;
+    font-size: 24rpx !important;
+    overflow: hidden; /* 隐藏超出容器的内容 */
+    text-overflow: ellipsis; /* 显示省略号 */
+    margin-bottom: 16rpx;
+  }
+}
+</style>

+ 159 - 0
src/pages/my/dynamic/components/my_list.vue

@@ -0,0 +1,159 @@
+<template>
+  <view class="list-box" v-if="dataList && dataList.length > 0">
+    <view
+      class="item"
+      v-for="(item, index) in dataList"
+      :key="index"
+      @click="goDetails(item)"
+    >
+      <view class="item-image">
+        <image :src="item.coverUrl" mode="aspectFill"></image>
+      </view>
+      <view class="item-content">
+        <view class="item-title">
+          <text>{{ item.title }}</text>
+          <view
+            v-if="type != 'merchant'"
+            :class="{
+              'service-status': item.auditStatus != 1,
+              'reserve-status': item.auditStatus == 1,
+            }"
+            >{{ item.auditStatus == 2 ? '已审核' : '审核中' }}</view
+          >
+        </view>
+        <view class="item-service">
+          <view class="service-info">
+            <text class="service-time">{{ item.publishTime }}</text>
+          </view>
+          <view class="diamond-count">
+            <image
+              src="/static/discover/icon_eye.png"
+              mode=""
+              class="eye-icon"
+            />
+            <text>{{ item.viewCount }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'my-list',
+  props: {
+    type: {
+      type: String,
+      default: 'user',
+    },
+    dataList: {
+      type: Array,
+      default: () => [],
+    },
+  },
+  methods: {
+    goDetails(item) {
+      let str = uni.$u.queryParams({
+        momentId: item.id,
+      })
+      uni.navigateTo({
+        url: `/pages/discover/details${str}`,
+      })
+    },
+  },
+}
+</script>
+
+<style scoped lang="scss">
+.list-box {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  padding: 26rpx;
+
+  .item {
+    width: calc(50% - 14rpx);
+    margin-bottom: 28rpx;
+    box-sizing: border-box;
+    background: #ffffff;
+    box-shadow: 0rpx 4rpx 12rpx 0rpx rgba(88, 209, 187, 0.1);
+    border-radius: 12rpx;
+    overflow: hidden;
+
+    .item-image {
+      width: 100%;
+      height: 200rpx;
+      background-color: #f0f0f0;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      overflow: hidden;
+
+      image {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+    }
+
+    .item-content {
+      padding: 20rpx;
+
+      .item-title {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 16rpx;
+
+        text:first-child {
+          font-size: 28rpx;
+          font-weight: bold;
+          color: #333;
+        }
+
+        .service-status {
+          font-size: 24rpx;
+          color: #03c8be;
+        }
+
+        .reserve-status {
+          font-size: 24rpx;
+          color: #f16336;
+        }
+      }
+
+      .item-service {
+        display: flex;
+
+        .service-info {
+          display: flex;
+          flex: 1;
+
+          .service-time {
+            display: block;
+            font-size: 24rpx;
+            color: #999999;
+          }
+        }
+
+        .diamond-count {
+          display: flex;
+          align-items: center;
+
+          image {
+            width: 22rpx;
+            height: 18rpx;
+            margin-right: 8rpx;
+          }
+
+          text {
+            font-size: 24rpx;
+            color: #999999;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 195 - 0
src/pages/my/dynamic/index.vue

@@ -0,0 +1,195 @@
+<template>
+  <view class="page-container">
+    <z-paging
+      ref="paging"
+      v-model="dynamicList"
+      :paging-style="{
+        background: '#F7FFFF',
+      }"
+      @query="queryList"
+    >
+      <template #top>
+        <view class="tab-box">
+          <view
+            v-for="(item, index) in tabList"
+            :key="index"
+            :class="{ item: true, active: current === index }"
+            @click="onTabChange(index)"
+          >
+            {{ item.name }}
+          </view>
+        </view>
+      </template>
+
+      <!-- 我的动态 -->
+      <my-dynamic-list
+        v-if="current === 0"
+        :data-list="dynamicList"
+      ></my-dynamic-list>
+
+      <!-- 草稿动态 -->
+      <draft-dynamic-list
+        v-if="current === 1"
+        :data-list="dynamicList"
+      ></draft-dynamic-list>
+
+      <template v-slot:empty>
+        <u-empty
+          :text="!isLogin ? '您还没有登录' : '您还没有相关动态呦~'"
+          icon="/static/common/logo1.png"
+        >
+          <u-button
+            size="small"
+            text="去登录"
+            color="linear-gradient(to right, #35c99d, #27aea7)"
+            :customStyle="{ marginTop: '20rpx' }"
+            v-if="!isLogin"
+            @click="goLogin"
+          >
+          </u-button>
+        </u-empty>
+      </template>
+    </z-paging>
+
+    <!-- 发布动态 -->
+    <view v-if="isLogin && current === 0" class="add-btn" @click="goPublish">
+      <image src="/static/discover/subtract.png" mode="" />
+    </view>
+  </view>
+</template>
+
+<script>
+import { myDynamicList, draftDynamicList } from '@/api/dynamic.js'
+import MyDynamicList from './components/my_list.vue'
+import DraftDynamicList from './components/draft_list.vue'
+
+export default {
+  components: {
+    MyDynamicList,
+    DraftDynamicList,
+  },
+  data() {
+    return {
+      isLogin: false,
+      dynamicList: [],
+      current: 0,
+      tabList: [
+        {
+          name: '我的动态',
+          value: -1,
+        },
+        {
+          name: '草稿箱',
+          value: 0,
+        },
+      ],
+    }
+  },
+  onLoad() {
+    this.isLogin = !!uni.getStorageSync('wx_copenid')
+  },
+  onShow() {
+    this.queryList(1, 10)
+  },
+  methods: {
+    queryList(pageNo, pageSize) {
+      let params = {
+        pageNum: pageNo,
+        pageSize: pageSize,
+      }
+      const api = this.current == 0 ? myDynamicList : draftDynamicList
+      api(params).then(res => {
+        if (
+          res.data.code === 200 &&
+          res.data.data.records &&
+          res.data.data.records.length > 0
+        ) {
+          let data = res.data.data.records
+          data.forEach(item => {
+            item.publishTime = item.publishTime
+              .replace('T', ' ')
+              .substring(0, 16)
+          })
+          this.$refs.paging.complete(data)
+        } else {
+          this.$refs.paging.complete([])
+        }
+      })
+    },
+    onTabChange(index) {
+      this.current = index
+      this.$refs.paging.reload()
+    },
+    goLogin() {
+      uni.navigateTo({
+        url: '/pages/login/wxLogin',
+      })
+    },
+    goPublish() {
+      uni.navigateTo({
+        url: '/pages/my/dynamic/add_dynamic',
+      })
+    },
+  },
+}
+</script>
+
+<style scoped lang="scss">
+.page-container {
+  background-color: #f7ffff;
+
+  .tab-box {
+    height: 90rpx;
+    display: flex;
+    justify-content: start;
+    align-items: center;
+    font-size: 28rpx;
+    color: #333333;
+    padding-left: 20rpx;
+    background: linear-gradient(180deg, #c1fff8 0%, #b8fff3 34%, #f5ffff 100%);
+    z-index: 999;
+
+    .item {
+      height: 55%;
+      padding: 0 12rpx;
+      display: flex;
+      align-items: center;
+    }
+
+    .active {
+      font-weight: 500;
+      font-size: 30rpx;
+      color: #03c8be;
+      position: relative;
+
+      &::after {
+        content: '';
+        width: 48rpx;
+        height: 4rpx;
+        position: absolute;
+        bottom: 0;
+        left: 50%;
+        transform: translateX(-50%);
+        background-color: #03c8be;
+      }
+    }
+  }
+
+  .add-btn {
+    position: fixed;
+    bottom: 50rpx;
+    right: 40rpx;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-color: #fff;
+    border-radius: 50%;
+    z-index: 99;
+
+    image {
+      width: 80rpx;
+      height: 80rpx;
+    }
+  }
+}
+</style>

+ 102 - 0
src/pages/my/dynamic/jsDynamic.vue

@@ -0,0 +1,102 @@
+<template>
+  <view class="page-container">
+    <z-paging
+      ref="paging"
+      v-model="dynamicList"
+      :paging-style="{
+        background: '#F7FFFF',
+      }"
+      @query="queryList"
+    >
+      <!-- 我的动态 -->
+      <my-dynamic-list :data-list="dynamicList"></my-dynamic-list>
+
+      <template v-slot:empty>
+        <u-empty
+          :text="!isLogin ? '您还没有登录' : '您还没有相关动态呦~'"
+          icon="/static/common/logo1.png"
+        >
+          <u-button
+            size="small"
+            text="去登录"
+            color="linear-gradient(to right, #35c99d, #27aea7)"
+            :customStyle="{ marginTop: '20rpx' }"
+            v-if="!isLogin"
+            @click="goLogin"
+          >
+          </u-button>
+        </u-empty>
+      </template>
+    </z-paging>
+  </view>
+</template>
+
+<script>
+import { myDynamicList } from '@/api/dynamic.js'
+import MyDynamicList from './components/my_list.vue'
+
+export default {
+  components: {
+    MyDynamicList,
+  },
+  data() {
+    return {
+      isLogin: false,
+      dynamicList: [],
+    }
+  },
+  onLoad() {
+    this.isLogin = !!uni.getStorageSync('wx_copenid')
+  },
+  methods: {
+    queryList(pageNo, pageSize) {
+      let params = {
+        pageNum: pageNo,
+        pageSize: pageSize,
+      }
+      myDynamicList(params).then(res => {
+        if (
+          res.data.code === 200 &&
+          res.data.data.records &&
+          res.data.data.records.length > 0
+        ) {
+          let data = res.data.data.records
+          data.forEach(item => {
+            item.publishTime = item.publishTime
+              .replace('T', ' ')
+              .substring(0, 16)
+          })
+          this.$refs.paging.complete(data)
+        } else {
+          this.$refs.paging.complete([])
+        }
+      })
+    },
+  },
+}
+</script>
+
+<style scoped lang="scss">
+.page-container {
+  background-color: #f7ffff;
+
+  .tab-box {
+    height: 90rpx;
+    display: flex;
+    justify-content: start;
+    align-items: center;
+    font-size: 28rpx;
+    color: #333333;
+    padding-left: 20rpx;
+    background: linear-gradient(180deg, #c1fff8 0%, #b8fff3 34%, #f5ffff 100%);
+    z-index: 999;
+
+    .item {
+      height: 55%;
+      padding: 0 12rpx;
+      display: flex;
+      align-items: center;
+    }
+  }
+}
+</style>

+ 2 - 2
src/pages/order/order.vue

@@ -39,7 +39,7 @@
 							<!-- 剩余<u-count-down :time="item.remainingTime" @finish="" v-if="item.dtCreateTime>0"></u-count-down> -->
 						</view>
 						<view class="button">
-							<text class="pay" @click="onCancleOrder(item)" v-if="[-1, 0, 1, 2, 3, 6].includes(item.nStatus)">取消订单</text>
+							<text class="pay" @click="onCancleOrder(item)" v-if="[0, 1, 2, 3, 6].includes(item.nStatus)">取消订单</text>
 							<text class="pay" @click="onCancleApply(item)" v-if="item.nStatus == 7">取消申请</text>
 							<text class="pay" @click="onPayOrder(item)" v-if="item.nStatus == -1 && item.nStatus < 1">支付订单</text>
 							<text class="pay" @click="goEvaluate(item)" v-if="item.nStatus == 4">去评价</text>
@@ -80,7 +80,7 @@
 	    	    <uni-tr v-for="(item, index) in cancelOrderPopup.orderList" :key="index">
 	    	    	<uni-td align="center"> {{ item.cTitle }} </uni-td>
 	    	    	<uni-td align="center"> {{ item.dYuanPrice }} </uni-td>
-	    	    	<uni-td align="center"> {{ item.nMinute }} </uni-td>
+	    	    	<uni-td align="center"> {{ item.serviceDuration }} </uni-td>
 	    	    </uni-tr>
 	        </uni-table>
           <u--textarea v-model="cancelOrderPopup.reason" placeholder="请输入退单原因" count maxlength="50" class="reson"></u--textarea>

+ 18 - 11
src/pages/order/order_detail.vue

@@ -9,7 +9,7 @@
 							¥<text>{{details.dTotalMoney}}</text>
 						</text>
 					</view>
-					<view class="time" v-if="nStatus == -3">
+					<view class="time" v-if="details.nStatus == -3">
 						拒绝原因: {{details.reasonRefusal}}
 					</view>
 					<view class="time" v-else>
@@ -307,7 +307,11 @@
 			return {
 				cAddress: '',
 				id: '',
-				details: '',
+				details: {
+          js: {
+            cNickName: ''
+          }
+        },
 				dtCreateTime: '',
 				dtCreateTime1: '',
 				acceptanceTime: '',
@@ -359,7 +363,9 @@
 				let data = {}
 				data.openId = cOpenId
 				userComment(data).then(res => {
-					this.userCommentList = res.data.data.records
+          if (res.data.code == 200){
+					  this.userCommentList = res.data.data.records
+          }
 				})
 			},
 			dial(cPhone) {
@@ -704,14 +710,15 @@
 							this.dtCreateTime1 = res.data.data.records[0].dtCreateTime.replace(charToRemove, ' '),
 
 							// let acceptancetime = res.data.data.records[0].dtCreateTime.slice(0, 10)
-							this.acceptanceTime = res.data.data.records[0].acceptanceTime.replace(charToRemove, ' '),
-
-							this.reachTime = res.data.data.records[0].reachTime.replace(charToRemove, ' '),
-							this.startTime = res.data.data.records[0].startTime.replace(charToRemove, ' '),
-							this.endTime = res.data.data.records[0].endTime.replace(charToRemove, ' '),
-							this.cAddress = JSON.parse(res.data.data.records[0].cAddress).address + '(' + JSON.parse(res.data.data
-								.records[0].cAddress).address + ')' + res.data.data.records[0].atlasAdd
-						var status = res.data.data.records[0].nStatus
+							this.acceptanceTime = this.details.acceptanceTime ? this.details.acceptanceTime.replace(charToRemove, ' ') : ''
+
+							this.reachTime = this.details.reachTime ? this.details.reachTime.replace(charToRemove, ' ') : ''
+							this.startTime = this.details.startTime ? this.details.startTime.replace(charToRemove, ' ') : ''
+							this.endTime = this.details.endTime ? this.details.endTime.replace(charToRemove, ' ') : ''
+              if(this.details.cAddress){
+                this.cAddress = JSON.parse(this.details.cAddress).address + '(' + JSON.parse(this.details.cAddress).address + ')' + this.details.atlasAdd
+              }
+						var status = this.details.nStatus ? this.details.nStatus : 0
 						if (status == 2 || status == 3) {
 							// this.setData({
 							this.orderShow = true

+ 386 - 373
src/pages/settings/fare.vue

@@ -1,392 +1,405 @@
 <template>
-	<view class="page-container">
-		<view class="card switch-card">
-			<view class="label">
-				免车费({{ isFree ? '生效中' : '未开启' }})
-			</view>
-			<u-switch  
-				size="24"
-				v-model="isFree" 
-				activeColor="#1ecbc3"
-				@change="switchChange"
-			></u-switch>
-		</view>
-
-		<view class="card form-card" v-if="isFree">
-			<u-cell-group :border="false">
-				<u-cell 
-					title="服务项目" 
-					:value="formData.projectNames || '请选择'" 
-					isLink 
-					:border="true"
-					@click="showProjectPicker = true"
-				></u-cell>
-
-				<u-cell 
-					title="服务时间1" 
-					:value="formData.timeRange1 || '请选择'" 
-					isLink 
-					:border="true"
-					@click="openTimePicker('timeRange1')"
-				></u-cell>
-
-				<u-cell 
-					title="服务时间2" 
-					:value="formData.timeRange2 || '请选择'" 
-					isLink 
-					:border="true"
-					@click="openTimePicker('timeRange2')"
-				></u-cell>
-
-				<u-cell title="订单距离" :border="false">
-					<view slot="value" class="input-slot">
-						<u-input
-							v-model="formData.minDistance"
-							placeholder="请输入最高免除距离"
-							border="none"
-							inputAlign="right"
-							type="number"
-							fontSize="15px"
-							placeholderStyle="font-size: 14px; color: #c0c4cc;"
-						>
-							<template slot="suffix">
-								<text class="unit-text" v-if="formData.minDistance">km</text>
-							</template>
-						</u-input>
-					</view>
-				</u-cell>
-			</u-cell-group>
-		</view>
-
-		<view class="tips-card" v-if="isFree">
-			<view class="tips-title">温馨提示:</view>
-			<view class="tips-content">
-				1.此处设置的免车费订单距离是客户距离你的直线距离,直线距离的车行距离在地理位置特殊性上可能有一定误差,望大家设置距离时知晓。
-			</view>
-			<view class="tips-content">
-				2.超出距离xxkm后,公司固定收取车费比例的10%管理费。
-			</view>
-		</view>
-
-		<u-gap height="100"></u-gap>
-
-		<view class="footer-bar">
-      <u-button 
-				text="返回" 
-				color="#666" 
-				shape="circle" 
-				plain
-				customStyle="height: 48px; font-size: 16px; margin-right: 20px;"
-				@click="onBack"
-			></u-button>
-			<u-button 
-				text="保存" 
-				color="#1ecbc3" 
-				shape="circle" 
-				customStyle="height: 48px; font-size: 16px;"
-				@click="onSave"
-			></u-button>
-		</view>
-
-		<u-picker 
-			:show="showProjectPicker" 
-			:columns="projectList" 
-			confirmColor="#1ecbc3"
-			@confirm="confirmProject"
-			@cancel="showProjectPicker = false"
-		></u-picker>
-
-		<u-picker
-			:show="showTimePicker"
-			:columns="timeColumns"
-			confirmColor="#1ecbc3"
-			@confirm="confirmTime"
-			@cancel="showTimePicker = false"
-		></u-picker>
-
-	</view>
+  <view class="page-container">
+    <view class="card switch-card">
+      <view class="label"> 免车费({{ isFree ? '生效中' : '未开启' }}) </view>
+      <u-switch
+        size="24"
+        v-model="isFree"
+        activeColor="#1ecbc3"
+        @change="switchChange"
+      ></u-switch>
+    </view>
+
+    <view class="card form-card" v-if="isFree">
+      <u-cell-group :border="false">
+        <u-cell
+          title="服务项目"
+          :value="formData.projectNames || '请选择'"
+          isLink
+          :border="true"
+          @click="showProjectPicker = true"
+        ></u-cell>
+
+        <u-cell
+          title="服务时间1"
+          :value="formData.timeRange1 || '请选择'"
+          isLink
+          :border="true"
+          @click="openTimePicker('timeRange1')"
+        ></u-cell>
+
+        <u-cell
+          title="服务时间2"
+          :value="formData.timeRange2 || '请选择'"
+          isLink
+          :border="true"
+          @click="openTimePicker('timeRange2')"
+        ></u-cell>
+
+        <u-cell title="订单距离" :border="false">
+          <view slot="value" class="input-slot">
+            <u-input
+              v-model="formData.minDistance"
+              placeholder="请输入最高免除距离"
+              border="none"
+              inputAlign="right"
+              type="number"
+              fontSize="15px"
+              placeholderStyle="font-size: 14px; color: #c0c4cc;"
+            >
+              <template slot="suffix">
+                <text class="unit-text" v-if="formData.minDistance">km</text>
+              </template>
+            </u-input>
+          </view>
+        </u-cell>
+      </u-cell-group>
+    </view>
+
+    <view class="tips-card" v-if="isFree">
+      <view class="tips-title">温馨提示:</view>
+      <view class="tips-content">
+        1.此处设置的免车费订单距离是客户距离你的直线距离,直线距离的车行距离在地理位置特殊性上可能有一定误差,望大家设置距离时知晓。
+      </view>
+      <view class="tips-content">
+        2.超出距离xxkm后,公司固定收取车费比例的10%管理费。
+      </view>
+    </view>
+
+    <u-gap height="100"></u-gap>
+
+    <view v-if="isFree" class="footer-bar">
+      <u-button
+        text="保存"
+        color="#1ecbc3"
+        shape="circle"
+        customStyle="height: 48px; font-size: 16px;"
+        @click="onSave"
+      ></u-button>
+    </view>
+
+    <u-picker
+      title="选择项目"
+      :show="showProjectPicker"
+      :columns="projectList"
+      confirmColor="#1ecbc3"
+      @confirm="confirmProject"
+      @cancel="showProjectPicker = false"
+    ></u-picker>
+
+    <u-picker
+      title="选择时间"
+      :show="showTimePicker"
+      :columns="timeColumns"
+      confirmColor="#1ecbc3"
+      @confirm="confirmTime"
+      @cancel="showTimePicker = false"
+    ></u-picker>
+  </view>
 </template>
 
 <script>
-import { getProjectByJsId } from '@/api/index';
-import { freeFareGet, freeFareAdd } from '@/api/staff';
+import { getProjectByJsId } from '@/api/index'
+import { freeFareGet, freeFareAdd, freeFareDisable } from '@/api/staff'
 export default {
-    data() {
-        return {
-            isFree: false, // 开关状态
-            
-            // 表单数据
-            formData: {
-                projectNames: '',
-                projectId: '',
-                timeRange1: '',
-                timeRange2: '',
-                minDistance: ''
-            },
-            
-            // 弹窗控制
-            showProjectPicker: false,
-            showTimePicker: false,
-            activeTimeField: '', // 当前正在编辑的时间字段名
-            
-            projectData: [],
-            projectList: [[]],
-            // 复杂时间选择器数据列
-            timeColumns: [],
-            openId: ''
+  data() {
+    return {
+      isFree: false, // 开关状态
+
+      // 表单数据
+      formData: {
+        projectNames: '',
+        projectId: '',
+        timeRange1: '',
+        timeRange2: '',
+        minDistance: '',
+      },
+
+      // 弹窗控制
+      showProjectPicker: false,
+      showTimePicker: false,
+      activeTimeField: '', // 当前正在编辑的时间字段名
+
+      projectData: [],
+      projectList: [[]],
+      // 复杂时间选择器数据列
+      timeColumns: [],
+      openId: '',
+    }
+  },
+  onLoad() {
+    // 初始化时间选择器的数据
+    this.initTimePickerData()
+    this.openId = uni.getStorageSync('wx_copenid')
+    if (this.openId) {
+      // 详情
+      this.getFreeFareRule(this.openId)
+      // 获取技师服务项目
+      this.getProject(this.openId)
+    }
+  },
+  methods: {
+    getFreeFareRule(openId) {
+      freeFareGet({ openId })
+        .then(res => {
+          if (res.data.code === 200 && res.data.data) {
+            this.formData = res.data.data
+            this.isFree = this.formData.enable == '1' ? true : false
+          } else {
+            this.isFree = false
+          }
+        })
+        .catch(err => {
+          console.error('获取免车费规则失败:', err)
+        })
+    },
+    getProject(id) {
+      getProjectByJsId({ openId: id }).then(res => {
+        if (res.data.code == 200) {
+          this.projectData = res.data.data
+          this.projectList = [res.data.data.map(item => item.cTitle)]
         }
+      })
+    },
+    // 初始化 5 列时间数据
+    initTimePickerData() {
+      // 1. 生成小时 (00时 - 23时)
+      const hours = []
+      for (let i = 0; i < 24; i++) {
+        hours.push((i < 10 ? '0' + i : i) + '时')
+      }
+
+      // 2. 生成分钟 (00分 - 59分)
+      const minutes = []
+      for (let i = 0; i < 60; i++) {
+        minutes.push((i < 10 ? '0' + i : i) + '分')
+      }
+
+      // 3. 组装5列:[开始时, 开始分, '至', 结束时, 结束分]
+      this.timeColumns = [
+        hours,
+        minutes,
+        ['至'], // 中间分隔符列
+        hours,
+        minutes,
+      ]
+    },
+    // 开关切换
+    switchChange(val) {
+      if (!val) {
+        // 关闭时清空或保持数据根据业务需求
+        this.formData = {}
+        this.onDisable()
+      }
+    },
+    // 确认服务项目
+    confirmProject(e) {
+      this.formData.projectNames = e.value[0]
+      const selectedProject = this.projectData.find(
+        item => item.cTitle === e.value[0]
+      )
+      if (selectedProject) this.formData.projectId = selectedProject.cId
+      this.showProjectPicker = false
+    },
+
+    // 打开时间选择
+    openTimePicker(field) {
+      this.activeTimeField = field
+      this.showTimePicker = true
+      // TODO: 这里可以根据 this.formData[field] 的值去设置 defaultIndex (回显选中位置)
+      // 如果需要回显,需要解析字符串 "09:00-21:00" 并计算索引
+    },
+    // 确认时间
+    confirmTime(e) {
+      const values = e.value // 拿到选中的数组,如 ["09时", "00分", "至", "21时", "30分"]
+
+      // 提取数字部分
+      const startHour = values[0].replace('时', '')
+      const startMin = values[1].replace('分', '')
+      const endHour = values[3].replace('时', '')
+      const endMin = values[4].replace('分', '')
+
+      // 拼接格式
+      const timeString = `${startHour}:${startMin}-${endHour}:${endMin}`
+
+      // 赋值给当前激活的字段
+      if (this.activeTimeField) {
+        this.formData[this.activeTimeField] = timeString
+
+        // 检查时间重叠
+        this.checkTimeOverlap()
+      }
+
+      this.showTimePicker = false
+    },
+    // 将时间范围字符串解析为开始和结束的分钟数
+    parseTimeRange(timeRange) {
+      if (!timeRange) return null
+
+      const [start, end] = timeRange.split('-')
+      const [startHour, startMinute] = start.split(':').map(Number)
+      const [endHour, endMinute] = end.split(':').map(Number)
+
+      return {
+        start: startHour * 60 + startMinute,
+        end: endHour * 60 + endMinute,
+      }
     },
-    onLoad() {
-        // 初始化时间选择器的数据
-        this.initTimePickerData();
-        this.openId = uni.getStorageSync('wx_copenid');
-        if (this.openId) {
-            // 详情
-            this.getFreeFareRule(this.openId);
-            // 获取技师服务项目
-            this.getProject(this.openId);
+
+    // 检查两个时间范围是否重叠
+    checkTimeOverlap() {
+      const range1 = this.parseTimeRange(this.formData.timeRange1)
+      const range2 = this.parseTimeRange(this.formData.timeRange2)
+
+      // 如果两个时间都已设置,则检查重叠
+      if (range1 && range2) {
+        // 检查是否重叠
+        const isOverlap = range1.start < range2.end && range2.start < range1.end
+
+        if (isOverlap) {
+          uni.showToast({
+            title: '两个服务时间不能重合',
+            icon: 'none',
+            duration: 2000,
+          })
+
+          // 清空当前设置的时间
+          this.formData[this.activeTimeField] = ''
         }
+
+        return isOverlap
+      }
+
+      return false
     },
-    methods: {
-        getFreeFareRule(openId) {
-            freeFareGet({ openId }).then(res => {
-                if (res.data.code === 200 && res.data.data) {
-                    this.formData = res.data.data;
-                    this.isFree = this.formData.enable == '1' ? true : false;
-                } else {
-                    this.isFree = false;
-                }
-            }).catch(err => {
-                console.error('获取免车费规则失败:', err);
-            });
-        },
-        getProject(id) {
-            getProjectByJsId({ openId: id }).then(res => {
-                if (res.data.code == 200) {
-                    this.projectData = res.data.data;
-                    this.projectList = [res.data.data.map(item => item.cTitle)];
-                }
+
+    // 保存
+    onSave() {
+      if (this.isFree) {
+        if (!this.formData.projectNames || !this.formData.minDistance) {
+          uni.showToast({
+            title: '请完善配置信息',
+            icon: 'none',
+          })
+          return
+        }
+
+        // 校验服务时间
+        if (this.checkTimeOverlap()) {
+          return
+        }
+      }
+      const params = {
+        ...this.formData,
+        openId: this.openId,
+        enable: this.isFree ? '1' : '0',
+      }
+      freeFareAdd(params).then(res => {
+        if (res.data.code === 200) {
+          if (this.isFree) {
+            uni.showToast({
+              title: '提交成功',
+              icon: 'none',
             })
-        },
-        // 初始化 5 列时间数据
-        initTimePickerData() {
-            // 1. 生成小时 (00时 - 23时)
-            const hours = [];
-            for (let i = 0; i < 24; i++) {
-                hours.push((i < 10 ? '0' + i : i) + '时');
-            }
-            
-            // 2. 生成分钟 (00分 - 59分)
-            const minutes = [];
-            for (let i = 0; i < 60; i++) {
-                minutes.push((i < 10 ? '0' + i : i) + '分');
-            }
-            
-            // 3. 组装5列:[开始时, 开始分, '至', 结束时, 结束分]
-            this.timeColumns = [
-                hours,
-                minutes,
-                ['至'], // 中间分隔符列
-                hours,
-                minutes
-            ];
-        },
-        // 开关切换
-        switchChange(val) {
-            if(!val) {
-                // 关闭时清空或保持数据根据业务需求
-                console.log('功能已关闭');
-            }
-        },
-        // 确认服务项目
-        confirmProject(e) {
-            this.formData.projectNames = e.value[0];
-            const selectedProject = this.projectData.find(item => item.cTitle === e.value[0]);
-            if (selectedProject) this.formData.projectId = selectedProject.cId;
-            this.showProjectPicker = false;
-        },
-
-        // 打开时间选择
-        openTimePicker(field) {
-            this.activeTimeField = field;
-            this.showTimePicker = true;
-            // TODO: 这里可以根据 this.formData[field] 的值去设置 defaultIndex (回显选中位置)
-            // 如果需要回显,需要解析字符串 "09:00-21:00" 并计算索引
-        },
-        // 确认时间
-        confirmTime(e) {
-            const values = e.value; // 拿到选中的数组,如 ["09时", "00分", "至", "21时", "30分"]
-				
-            // 提取数字部分
-            const startHour = values[0].replace('时', '');
-            const startMin = values[1].replace('分', '');
-            const endHour = values[3].replace('时', '');
-            const endMin = values[4].replace('分', '');
-            
-            // 拼接格式
-            const timeString = `${startHour}:${startMin}-${endHour}:${endMin}`;
-            
-            // 赋值给当前激活的字段
-            if(this.activeTimeField) {
-                this.formData[this.activeTimeField] = timeString;
-                
-                // 检查时间重叠
-                this.checkTimeOverlap();
-            }
-            
-            this.showTimePicker = false;
-        },
-        
-        // 将时间范围字符串解析为开始和结束的分钟数
-        parseTimeRange(timeRange) {
-            if (!timeRange) return null;
-            
-            const [start, end] = timeRange.split('-');
-            const [startHour, startMinute] = start.split(':').map(Number);
-            const [endHour, endMinute] = end.split(':').map(Number);
-            
-            return {
-                start: startHour * 60 + startMinute,
-                end: endHour * 60 + endMinute
-            };
-        },
-        
-        // 检查两个时间范围是否重叠
-        checkTimeOverlap() {
-            const range1 = this.parseTimeRange(this.formData.timeRange1);
-            const range2 = this.parseTimeRange(this.formData.timeRange2);
-            
-            // 如果两个时间都已设置,则检查重叠
-            if (range1 && range2) {
-                // 检查是否重叠
-                const isOverlap = range1.start < range2.end && range2.start < range1.end;
-                
-                if (isOverlap) {
-                    uni.showToast({
-                        title: '两个服务时间不能重合',
-                        icon: 'none',
-                        duration: 2000
-                    });
-                    
-                    // 清空当前设置的时间
-                    this.formData[this.activeTimeField] = '';
-                }
-                
-                return isOverlap;
-            }
-            
-            return false;
-        },
-
-        // 保存
-        onSave() {
-            if(this.isFree) {
-                if(!this.formData.projectNames || !this.formData.minDistance) {
-                    uni.showToast({
-                        title: '请完善配置信息',
-                        icon: 'none'
-                    });
-                    return;
-                }
-                
-                // 校验服务时间
-                if (this.checkTimeOverlap()) {
-                    return;
-                }
-            }
-            const params = {
-                ...this.formData,
-                openId: this.openId,
-                enable: this.isFree ? '1' : '0'
-            };
-            freeFareAdd(params)
-                .then(res => {
-                    if (res.data.code === 200) {
-                        uni.showToast({
-                            title: '提交成功',
-                            icon: 'none'
-                        });
-                        
-                        setTimeout(() => {
-                            uni.navigateBack();
-                        }, 1000);
-                    } else {
-                        uni.showToast({
-                            title: res.data.msg,
-                            icon: 'none'
-                        });
-                    }
-                })
-        },
-        onBack() {
-          uni.navigateBack({
-            delta: 1
-          });
+
+            setTimeout(() => {
+              uni.navigateBack({
+                delta: 1,
+              })
+            }, 1000)
+          }
+        } else {
+          uni.showToast({
+            title: res.data.msg,
+            icon: 'none',
+          })
         }
-    }
+      })
+    },
+    onDisable() {
+      const params = {
+        openId: this.openId,
+        enable: '0',
+      }
+      freeFareDisable(params).then(res => {
+        if (res.data.code === 200) {
+        } else {
+          uni.showToast({
+            title: res.data.msg,
+            icon: 'none',
+          })
+        }
+      })
+    },
+    onBack() {
+      uni.navigateBack({
+        delta: 1,
+      })
+    },
+  },
 }
 </script>
 
 <style lang="scss" scoped>
 .page-container {
-    min-height: 100vh;
-    padding: 20rpx;
-    padding-bottom: 120rpx; 
-    background-color: #F6F7F9;
-
-	.card {
-		background-color: #fff;
-		border-radius: 16rpx;
-		margin-bottom: 20rpx;
-		overflow: hidden; 
-	}
-
-	.switch-card {
-		padding: 30rpx;
-		display: flex;
-		justify-content: space-between;
-		align-items: center;
-		
-		.label {
-			font-size: 30rpx;
-			color: #333;
-		}
-	}
-
-    .form-card {
-        .input-slot {
-            width: 300rpx;
-            display: flex;
-            justify-content: flex-end;
-            align-items: center;
-        }
-        
-        .unit-text {
-            color: #333;
-            margin-left: 4rpx;
-            font-size: 30rpx;
-        }
+  min-height: 100vh;
+  padding: 20rpx;
+  padding-bottom: 120rpx;
+  background-color: #f6f7f9;
+
+  .card {
+    background-color: #fff;
+    border-radius: 16rpx;
+    margin-bottom: 20rpx;
+    overflow: hidden;
+  }
+
+  .switch-card {
+    padding: 30rpx;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .label {
+      font-size: 30rpx;
+      color: #333;
+    }
+  }
+
+  .form-card {
+    .input-slot {
+      width: 300rpx;
+      display: flex;
+      justify-content: flex-end;
+      align-items: center;
+    }
+
+    .unit-text {
+      color: #333;
+      margin-left: 4rpx;
+      font-size: 30rpx;
+    }
+  }
+
+  .tips-card {
+    background-color: #fff;
+    border-radius: 16rpx;
+    padding: 30rpx;
+
+    .tips-title {
+      font-size: 28rpx;
+      font-weight: bold;
+      color: #333;
+      margin-bottom: 16rpx;
+    }
+
+    .tips-content {
+      font-size: 26rpx;
+      color: #666;
+      line-height: 1.6;
+      margin-bottom: 10rpx;
+      text-align: justify;
     }
-	
-	.tips-card {
-		background-color: #fff;
-		border-radius: 16rpx;
-		padding: 30rpx;
-		
-		.tips-title {
-			font-size: 28rpx;
-			font-weight: bold;
-			color: #333;
-			margin-bottom: 16rpx;
-		}
-		
-		.tips-content {
-			font-size: 26rpx;
-			color: #666;
-			line-height: 1.6;
-			margin-bottom: 10rpx;
-			text-align: justify;
-		}
-	}
+  }
 }
 </style>

+ 13 - 2
src/pages/staff/index.vue

@@ -107,6 +107,12 @@
 							<text>合同签订</text>
 						</view>
 					</u-grid-item>
+          <u-grid-item>
+						<view class="function" @click="goDiscover">
+							<image src="/static/discover/my-dynamic.png" mode=""></image>
+							<text>我的动态</text>
+						</view>
+					</u-grid-item>
 				</u-grid>
 			</view>
 		</view>
@@ -142,7 +148,7 @@
 				</view>
 			</view>
 		</view>
-		<view class="order">
+		<!-- <view class="order">
 			<view class="tab-box">
 				<u-tabs keyName="label" lineColor="#03C8BE" :list="tabList" @change="onTabChange"></u-tabs>
 			</view>
@@ -152,7 +158,7 @@
 				@query="queryList">
 
 			</z-paging>
-		</view>
+		</view> -->
 	</view>
 </template>
 
@@ -340,6 +346,11 @@
 			qrCodeShow() {
 				this.isQrcodeShow = !this.isQrcodeShow
 			},
+			goDiscover() {
+				uni.navigateTo({
+					url: '/pages/my/dynamic/index'
+				})
+			},
 			goEdit() {
 				uni.navigateTo({
 					url: '/pages/my/indent?title=编辑资料'

BIN
src/static/discover/cover_bg.png


BIN
src/static/discover/discover-cur.png


BIN
src/static/discover/discover.png


BIN
src/static/discover/ic_delete@2x.png


BIN
src/static/discover/ic_upload.png


BIN
src/static/discover/icon_eye.png


BIN
src/static/discover/icon_eye_cru.png


BIN
src/static/discover/icon_location.png


BIN
src/static/discover/my-dynamic.png


BIN
src/static/discover/subtract.png


+ 1041 - 0
src/uni_modules/cl-upload/components/cl-upload/cl-upload.vue

@@ -0,0 +1,1041 @@
+<template>
+	<view class="cl-updata">
+		<view class="file-list" :style="[listRowStyle]">
+
+			<view v-for="(item, index) in previewList" @tap="clickSelectedFile(item, index)" class="file-list-row"
+				:style="[rowStyle]" :key="index">
+
+				<image 
+					class="_image" 
+					v-if="fileUrlType(item) === 'image'" 
+					:src="item.path" 
+					:style="[imgStyle]" 
+					mode="aspectFill">
+				</image>
+
+				<view v-else class="_video" :style="[imgStyle]">
+
+					<!-- #ifdef MP-WEIXIN || MP-ALIPAY -->
+					<video 
+						v-if="!autoUpload || cloudType === 'other'" 
+						class="_video" 
+						:style="[imgStyle]"
+						:src="item.path" 
+						:show-center-play-btn="false" 
+						:show-fullscreen-btn="false"
+						:show-play-btn="false" 
+						:show-loading="false" 
+						:enable-progress-gesture="false" 
+						:controls="false">
+						<view @tap="previewVideo(item, index)" class="play">
+							<image style="width: 100%;" :src="playImg" mode="widthFix"></image>
+						</view>
+					</video>
+
+					<!-- #endif -->
+					
+					<!-- #ifdef APP-PLUS -->
+					<video
+						v-if="cloudType === 'other'" 
+						class="_video" 
+						:style="[imgStyle]" 
+						:src="item.path"
+						:poster="item.path"
+						:controls="false"
+						:show-center-play-btn="false" 
+						:show-fullscreen-btn="false" 
+						:show-play-btn="false"
+						:show-loading="false" 
+						:enable-progress-gesture="false">
+						<cover-image class="app_play" :src="playImg" @tap="previewVideo(item, index)"></cover-image>
+						<cover-view class="remove" v-if="remove" @tap="deleteSelectedFile(item, index)">
+							<cover-image class="image" :src="deleteImg" mode="widthFix" @tap="deleteSelectedFile(item, index)"></cover-image>
+						</cover-view>
+					</video>
+					<!-- #endif -->
+					
+					<!-- #ifndef MP-WEIXIN || MP-ALIPAY || APP-PLUS -->
+					<video 
+						v-if="cloudType === 'other'" 
+						class="_video" 
+						:autoplay="false"
+						:style="[imgStyle]" 
+						:src="item.path"
+						:controls="false"
+						:show-center-play-btn="false" 
+						:show-fullscreen-btn="false" 
+						:show-play-btn="false"
+						:show-loading="false" 
+						:enable-progress-gesture="false" >
+						<cover-view  @tap="previewVideo(item, index)" class="play">
+							<cover-image style="width: 100%;" :src="playImg" mode="widthFix"></cover-image>
+						</cover-view>
+					</video>
+					
+					<!-- #endif -->
+					
+					<template v-else>
+						<cl-image class="pay" :style="[imgStyle]" :cloudType="cloudType"
+							:src="(item.poster || item.path)"></cl-image>
+						
+						<view class="play" @tap="previewVideo(item, index)">
+							<image class="play-img" :src="playImg" mode="widthFix"></image>
+						</view>
+					</template>
+
+				</view>
+				
+				<view class="remove" v-if="remove" @tap.stop="deleteSelectedFile(item, index)">
+					<image class="image" :src="deleteImg" mode="widthFix"></image>
+				</view>
+			</view>
+
+			<view v-if="add && FileList.length < max" @tap="selectFileTypeOnAdd" :style="[rowStyle]" class="file-list-row">
+				<slot name="addImg">
+					<div class="add-image">
+						<image class="_image" :src="addImg" mode="widthFix"></image>
+					</div>
+				</slot>
+			</view>
+		</view>
+
+
+		<view v-if="tempVideoUrl" class="mask">
+			<image @tap="tempVideoUrl = ''" class="_root" :src="closeImg" mode="widthFix"></image>
+
+			<view class="block" @tap.stop>
+				<video class="block_video" autoplay :src="tempVideoUrl"></video>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import ClImage from '../cl-image/cl-image.vue'
+export default {
+	name: "cl-upload",
+	components: { ClImage },
+	props: {
+		//受控图片列表
+		// #ifdef VUE2
+		value: {
+			type: Array,
+			default: () => [],
+		},
+		// #endif
+
+		// #ifdef VUE3
+		modelValue: {
+			type: Array,
+			default: () => [],
+		},
+		// #endif
+
+		// 存储云类型 oss阿里云  vframe七牛云   process腾讯云  other其他
+		cloudType: {
+			type: String,
+			default: 'oss'
+		},
+		// 标识符,即后端接口参数名
+		fileName: {
+			type: String,
+			default: 'file'
+		},
+		// 文件类型 'image', 'video', 'all'
+		fileType: {
+			type: String,
+			default: 'all'
+		},
+		// 上传图片参数
+		imageFormData: {
+			type: Object,
+			default: () => {
+				return {}
+			}
+		},
+		// 上传视频参数
+		videoFromData: {
+			type: Object,
+			default: () => { }
+		},
+
+		// 必选参数,上传的地址
+		action: {
+			type: String,
+			default: ''
+		},
+		
+		// 启用目录, 仅unicloud阿里云支持
+		// https://uniapp.dcloud.net.cn/uniCloud/storage.html#storage-dir
+		cloudPathAsRealPath: {
+			type: Boolean,
+			default: false
+		},
+
+		// 设置上传的请求头部
+		headers: {
+			type: Object,
+			default: () => { }
+		},
+
+		// 上传时附带的额外参数
+		data: {
+			type: Object,
+			default: () => { }
+		},
+
+		// 是否开启预览图片
+		isPreviewImage: {
+			type: Boolean,
+			default: true
+		},
+
+		// 图片指示器样式,可取值:"default" - 底部圆点指示器; "number" - 顶部数字指示器; "none" - 不显示指示器。
+		indicator: {
+			type: String,
+			default: 'none'
+		},
+		// 是否在选取文件后立即进行上传	
+		autoUpload: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示删除按钮
+		remove: {
+			type: Boolean,
+			default: true
+		},
+		// 是否添加按钮
+		add: {
+			type: Boolean,
+			default: true
+		},
+		// 最多显示数量
+		max: {
+			type: Number,
+			default: 9
+		},
+		// 视频最大上传数量
+		maxVideo: {
+			type: Number,
+			default: 0
+		},
+		// 列表样式
+		listStyle: {
+			type: Object,
+			default: () => { }
+		},
+		// 删除提示弹窗标题
+		deleteTitle: {
+			type: String,
+			default: '提示'
+		},
+		// 删除提示弹窗文案
+		deleteText: {
+			type: String,
+			default: '您确认要删除吗?'
+		},
+		// 加载文案
+		loadingText: {
+			type: String,
+			default: '正在上传中...'
+		},
+		// 是否开启删除前钩子
+		useBeforeDelete: {
+			type: Boolean,
+			default: false
+		},
+		// 是否开启上传前钩子
+		useBeforeUpload: {
+			type: Boolean,
+			default: false
+		},
+		// 添加按钮图片
+		addImg: {
+			type: String,
+			default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/bb1550b3-e0a8-4a90-a86f-00f8c6afa9fb.png'
+		},
+		// 播放按钮图片
+		playImg: {
+			type: String,
+			default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/ae40402f-aa53-4344-b553-2322799bebd6.png'
+		},
+		// 删除按钮图片
+		deleteImg: {
+			type: String,
+			default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/d20177a5-417e-4c5d-a266-1988361c543d.png'
+		},
+		// 关闭视频按钮图片
+		closeImg: {
+			type: String,
+			default: 'https://mp-61599c79-d7ee-4a75-a24b-e5a288da6dd3.cdn.bspapp.com/cloudstorage/cde4362d-7ec7-4cac-a692-12e1f576be1e.png'
+		},
+	},
+	data() {
+		return {
+			// 渲染列表
+			FileList: [],
+
+			// 预览视频地址
+			tempVideoUrl: '',
+
+			// 临时文件列表
+			tempFile_paths: [],
+
+		};
+	},
+	watch: {
+		// #ifdef VUE2
+		'value': {
+			handler: function (newVal, oldVal) {
+				this.FileList = newVal;
+			},
+			deep: true,
+			immediate: true
+		},
+		// #endif
+
+		// #ifdef VUE3
+		'modelValue': {
+			handler: function (newVal, oldVal) {
+				this.FileList = newVal;
+			},
+			deep: true,
+			immediate: true
+		},
+		// #endif
+	},
+	computed: {
+		previewList() {
+			return this.FileList.map(item => {
+				return {
+					path: item.path || item,
+					poster: item.poster || ''
+				}
+			})
+		},
+		listRowStyle() {
+			const style = {
+				'grid-template-columns': `repeat(${this.listStyle?.columns || 4}, 1fr)`, // 每行数量	
+				'grid-column-gap': this.listStyle?.columnGap || '40rpx', // 行间距	
+				'grid-row-gap': this.listStyle?.rowGap || '40rpx', // 列间距
+				'padding': this.listStyle?.padding || '0rpx' // 列表内边距
+			}
+
+			return style;
+		},
+		rowStyle() {
+			const { height = '140rpx', ratio } = this.listStyle || {};
+			const style = {
+				'aspect-ratio': height ? '' : ratio || '1/1', // 图片比例
+				'height': height,
+			};
+
+			return style;
+		},
+
+		imgStyle() {
+			const style = {
+				'border-radius': this.listStyle?.radius || '6rpx', // 图片圆角
+			}
+			return style;
+		}
+	},
+	methods: {
+		/**
+		 * 删除已选择文件
+		 * @param {object} item 文件信息
+		 * @param {number} selectedFileIndex 文件索引
+		 * */
+		deleteSelectedFile(item, selectedFileIndex) {
+
+			const fileToDelete = this.FileList[selectedFileIndex];
+
+			// 删除前钩子
+			if (this.useBeforeDelete) {
+				this.$emit('beforeDelete', fileToDelete, selectedFileIndex, () => {
+					return deleteFileFromList()
+				})
+			}
+
+			if (!this.useBeforeDelete) {
+				uni.showModal({
+					title: this.deleteTitle,
+					content: this.deleteText,
+					success: (res) => {
+						if (res.confirm) {
+							deleteFileFromList()
+						}
+					}
+				});
+			}
+
+			const deleteFileFromList = () => {
+				const tempFileIndex = this.tempFile_paths.indexOf(item || item.path);
+
+				if (tempFileIndex > -1) { 
+					this.tempFile_paths.splice(tempFileIndex, 1)
+				}
+
+				this.FileList.splice(selectedFileIndex, 1) 
+
+        // 检查文件列表是否为空,如果为空则触发重置事件
+				if (this.FileList.length === 0) {
+					this.$emit('onError', '文件列表为空');
+				}
+        
+				// #ifdef VUE2 
+				this.$emit('input', this.FileList)
+				// #endif
+
+				// #ifdef VUE3
+				this.$emit("update:modelValue", this.FileList);
+				// #endif
+			}
+
+		},
+
+		/**
+		 * 点击已选择文件
+		 * @param {object} item 文件信息
+		 * @param {number} index 文件索引 
+		 * */
+		clickSelectedFile(item, index) {
+			this.previewImage(item?.path ?? item, index);
+			this.$emit('onImage', {
+				item,
+				index
+			})
+		},
+
+		/**
+		 * 点击选择图片按钮
+		 * */
+		selectFileTypeOnAdd() {
+
+			switch (this.fileType) {
+				case 'image':
+					this.handleFileSelection(1);
+					break;
+				case 'video':
+					this.handleFileSelection(2);
+					break;
+				case 'all':
+					uni.showActionSheet({
+						itemList: ['相册', '视频'],
+						success: (res) => {
+							const tapIndex = res.tapIndex;
+							if (tapIndex === 0) {
+                this.$emit('onMediaTypeSelect', 'image');
+								this.handleFileSelection(1);
+							} else {
+                this.$emit('onMediaTypeSelect', 'video');
+								this.handleFileSelection(2);
+							}
+						},
+						fail: (res) => {
+							console.error(res.errMsg);
+						}
+					});
+					break;
+				default:
+					this.handleFileSelection(1);
+					break;
+			}
+		},
+
+
+		/**
+		 * 从本地选择文件。
+		 * @param { number } updataType 选择类型 1:图片 2视频
+		 * */
+		async handleFileSelection(updataType) {
+			const that = this;
+			if (updataType === 1) {
+
+				const data = Object.assign({}, {
+					// 最多可以选择的图片张数,默认9
+					count: 9,
+					// 仅对 mediaType 为 image 时有效,是否压缩所选文件
+					// #ifndef MP-TOUTIAO
+					sizeType: ['original', 'compressed'],
+					// #endif
+					// album 从相册选图,camera 使用相机,默认二者都有。
+					sourceType: ['camera', 'album'],
+
+					compress: false
+				}, this.imageFormData)
+
+				data['count'] = this.max - this.FileList.length
+
+				uni.chooseImage({
+					...data,
+					success: async (res) => {
+						let tempFiles = res.tempFiles
+						const compress = that.imageFormData?.compress || false;
+
+						// 限制图片上传尺寸
+						if (that.imageFormData?.size ?? false) {
+							const maxSize = that.imageFormData.size * 1024 * 1024
+							
+							tempFiles.map((imgInfo, index) => {
+								if (imgInfo.size > maxSize) {
+									tempFiles.splice(index, 1)
+									that.$emit('onImageSize', imgInfo)
+									return uni.showToast({
+										title: `图片最大上传${that.imageFormData.size}MB`,
+										duration: 2000,
+										icon: 'none'
+									});
+								}
+							})
+						}
+
+						// 开启压缩图片
+						if (compress) {
+							const compressedImagePathList = tempFiles.map(imageItem => {
+								return that.compressImage(imageItem.path)
+							})
+
+							Promise.all(compressedImagePathList).then(result => {
+								upload(result);
+							})
+
+						} else {
+							upload(tempFiles);
+						}
+
+						function upload(tempImages) {
+							if (that.autoUpload) {
+								tempImages.map(item => {
+									that.onBeforeUploadFile(item, 'image')
+								})
+							} else {
+								that.FileList = [...that.FileList, ...tempImages]
+								tempImages.map(item => {
+									that.tempFile_paths.push(item)
+								})
+							}
+						}
+
+					},
+					fail(err) {
+						console.error('选择图片失败', err)
+						that.$emit('onError', err)
+					}
+
+				})
+			}
+
+			if (updataType === 2) {
+
+				// 限制视频最大上传数量
+				const VIDEO_REGEXP = /\.(mp4|flv|avi)/i
+				const videoList = await that.FileList.filter(item => {
+					const fileUrl = item?.url ?? item
+					return VIDEO_REGEXP.test(fileUrl)
+				})
+
+				if (that.maxVideo > 0 && videoList.length >= that.maxVideo) {
+					that.$emit('onVideoMax', that.maxVideo, videoList.length)
+					return uni.showToast({
+						title: '视频数量已超出',
+						duration: 2000,
+						icon: 'none'
+					});
+				}
+
+				const data = Object.assign({}, {
+					// 	拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。
+					maxDuration: 60,
+					// #ifndef MP-TOUTIAO
+					// 'front'、'back',默认'back'
+					camera: "back",
+					// #endif
+
+					// album 从相册选视频,camera 使用相机拍摄,默认二者都有。
+					sourceType: ['camera', 'album'],
+					// 是否压缩所选的视频源文件,默认值为 true,需要压缩。
+					compressed: true,
+					// 'front'、'back',默认'back'
+				}, this.videoFromData)
+
+				uni.chooseVideo({
+					...data,
+					success: (res) => {
+						let tempFilePath = { ...res }
+						tempFilePath['path'] = res.tempFilePath
+
+						// 限制视频上传尺寸
+						if (that.videoFromData?.size ?? false) {
+							const maxSize = that.videoFromData.size * 1024 * 1024
+
+							if (tempFilePath.size > maxSize) {
+								uni.showToast({
+									title: `视频最大上传${that.videoFromData.size}MB`,
+									duration: 2000,
+									icon: 'none'
+								});
+								return false;
+							}
+
+						}
+						if (that.autoUpload) {
+							that.onBeforeUploadFile(tempFilePath, 'video')
+						} else {
+							that.FileList.push(tempFilePath)
+							that.tempFile_paths.push(tempFilePath)
+						}
+					},
+					fail(err) {
+						console.error('选择视频失败', err)
+						that.$emit('onError', err)
+					}
+
+				})
+			}
+		},
+
+		/**
+		 * 上传前钩子
+		 * @param { tempFile } 临时文件
+		 * @return { Promise }
+		 * */ 
+		onBeforeUploadFile(tempFile) {
+			if (this.useBeforeUpload) {
+				return this.$emit('beforeUpload', tempFile, () => {
+					return this.updataFile(tempFile);
+				})
+			}
+			return this.updataFile(tempFile);
+		},
+
+		/**
+		 * 上传文件到服务器
+		 * @param { tempFile } 临时文件
+		 * @return { Promise }
+		 * */
+		updataFile(tempFile) {
+			const that = this;
+			const filePath = tempFile.path || tempFile;
+			const fileType = this.fileUrlType(filePath) == 'image' ? '.png' : '.mp4';
+			const fileName = tempFile.name || Date.now() + fileType;
+
+			uni.showLoading({
+				title: this.loadingText,
+				icon: 'loading'
+			})
+
+			return new Promise((resolve, reject) => {
+				// uniCloud上传
+				if (that.action === 'uniCloud') {
+
+					uniCloud.uploadFile({
+						cloudPath: String(fileName),
+						filePath: filePath,
+						// #ifdef MP-ALIPAY 
+						fileType: fileType,
+						// #endif
+						cloudPathAsRealPath: this.cloudPathAsRealPath,
+						
+						onUploadProgress: (progressEvent) => {
+							const percentCompleted = Math.round(
+								(progressEvent.loaded * 100) / progressEvent.total
+							);
+							that.$emit('onProgress', percentCompleted)
+						},
+						success(result) {
+							if (that.autoUpload) {
+								that.FileList.push(result.fileID)
+							} else {
+								that.FileList.map((item, index) => {
+									if (item === filePath || item.path === filePath) {
+										that.FileList.splice(index, 1, result.fileID)
+									}
+								})
+							}
+							
+							// #ifdef VUE2
+							that.$emit('input', that.FileList)
+							// #endif
+							// #ifdef VUE3
+							that.$emit("update:modelValue", that.FileList);
+							// #endif
+
+							resolve(result.fileID)
+							uni.hideLoading();
+							that.$emit('onProgress', {
+								...result
+							})
+						},
+						fail: (error) => {
+							uni.hideLoading();
+							console.error('error', error);
+							that.$emit('onError', error)
+							reject(error)
+						}
+					})
+					return false;
+				}
+
+				// 接口服务上传
+				const uploadTask = uni.uploadFile({
+					url: that.action,
+					filePath: filePath,
+					name: that.fileName,
+					formData: that.data,
+					header: that.headers,
+					// #ifdef MP-ALIPAY
+					fileType: filetype,
+					// #endif
+					success: (uploadFileRes) => {
+						const data = JSON.parse(uploadFileRes.data)
+						uni.hideLoading();
+						that.success(data)
+
+						if (!this.autoUpload) {
+							that.FileList.map((item, index) => {
+								if (item === filePath || item.path === filePath) {
+									that.FileList.splice(index, 1)
+								}
+							})
+						}
+
+						resolve(data)
+					},
+					fail: (error) => {
+						uni.hideLoading();
+						console.error('error', error);
+						that.$emit('onError', error)
+						reject(error)
+					}
+				});
+
+				uploadTask.onProgressUpdate((res) => {
+					that.$emit('onProgress', {
+						...res,
+						...tempFile
+					})
+				});
+			})
+		},
+
+		/**
+		 * 手动上传
+		 * */
+		submit() {
+
+			return new Promise((resolve, reject) => {
+				if (this.tempFile_paths.length <= 0) {
+					resolve([])
+				}
+
+				const uploadedFilePaths = this.tempFile_paths.map(item => {
+					return this.onBeforeUploadFile(item || item.path)
+				})
+
+				Promise.all(uploadedFilePaths).then(res => {
+					this.tempFile_paths = []
+					resolve(res)
+				}).catch(err => {
+					reject(err)
+				})
+			})
+
+		},
+
+		/**
+		 * 返回数据
+		 * @param {array} data 上传成功后的数据
+		 * @return {array} 返回数据
+		 * */
+		success(data) {
+			this.$emit('onSuccess', data);
+
+			// 自定义数据结构-选择性开启
+			// const list = data.map(item=> {
+			// 	return JSON.parse(item).data.link;
+			// })
+			// this.$emit('input', [...this.FileList, ...list]);
+		},
+		/**
+		 * 压缩图片
+		 * @param {array} tempFilePaths 临时路径数组
+		 * @return {array} 被压缩过的路径数组
+		 * */
+		async compressImage(tempFilePaths) {
+			const that = this;
+
+			return new Promise((resolve, reject) => {
+
+				if (typeof tempFilePaths !== 'string') {
+					console.error('压缩路径错误')
+					reject([])
+				}
+
+				uni.showLoading({
+					title: '压缩中...',
+					icon: 'loading',
+				})
+
+				// #ifdef H5
+				this.canvasDataURL(tempFilePaths, {
+					quality: that.imageFormData.quality / 100
+				}, (base64Codes) => {
+					resolve(base64Codes);
+					uni.hideLoading();
+				})
+				// #endif
+
+				// #ifndef H5
+				uni.compressImage({
+					src: tempFilePaths,
+					quality: that.imageFormData.quality || 80,
+					success: res => {
+						resolve(res.tempFilePath);
+						uni.hideLoading();
+					},
+					fail(err) {
+						reject(err);
+						uni.hideLoading();
+					}
+				})
+				// #endif
+
+			})
+		},
+
+		/**
+		 * H5压缩图片质量
+		 * @param {string} path 图片路径
+		 * @param {object} obj 压缩配置
+		 * @param {function} callback 回调函数
+		 * @return {string} base64
+		 * */
+		canvasDataURL(path, obj, callback) {
+			var img = new Image();
+			img.src = path;
+			img.onload = function () {
+				var that = this;
+				// 默认按比例压缩
+				var w = that.width,
+					h = that.height,
+					scale = w / h;
+				w = obj.width || w;
+				h = obj.height || (w / scale);
+				var quality = 0.8; // 默认图片质量为0.8
+				//生成canvas
+				var canvas = document.createElement('canvas');
+				var ctx = canvas.getContext('2d');
+				// 创建属性节点
+				var anw = document.createAttribute("width");
+				anw.nodeValue = w;
+				var anh = document.createAttribute("height");
+				anh.nodeValue = h;
+				canvas.setAttributeNode(anw);
+				canvas.setAttributeNode(anh);
+				ctx.drawImage(that, 0, 0, w, h);
+				// 图像质量
+				if (obj.quality && obj.quality <= 1 && obj.quality > 0) {
+					quality = obj.quality;
+				}
+				// quality值越小,所绘制出的图像越模糊
+				var base64 = canvas.toDataURL('image/jpeg', quality);
+				// 回调函数返回base64的值
+				callback(base64);
+			}
+		},
+
+		/**
+		 * 预览图片
+		 * @param {string, object} item 文件信息
+		 * */
+		previewImage(item) {
+			if (this.fileUrlType(item) === 'video') return false;
+			if (!this.isPreviewImage) return false;
+
+			const imgs = this.FileList.filter(item => {
+				return this.fileUrlType(item) !== 'video'
+			}).map(item => item?.path ?? item)
+			const current = imgs.indexOf(item || item.path);
+
+			uni.previewImage({
+				current: current,
+				urls: imgs,
+				success() {
+				},
+				fail(err) {
+					console.log(err);
+				}
+			})
+		},
+
+		/**
+		 * 预览视频
+		 * @param {string, object} item 文件信息
+		 * @param {number} index 索引
+		 * */
+		previewVideo(item, index) {
+			this.$emit('onVideo', {
+				item,
+				index
+			})
+			this.tempVideoUrl = item.path;
+		},
+
+		/**
+		 * 是否img类型
+		 * @param {string, object} item 文件信息
+		 * @return {boolean} 是否img类型
+		 * */
+		fileUrlType(file) {
+			const filePath = file.path || file;
+
+			if (this.isBase64(filePath)) return 'image'
+
+			const fileType = filePath.split('.').pop();
+
+			const IMAGE_REGEXP = /(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg|image)/i
+			if (IMAGE_REGEXP.test(fileType)) {
+				return 'image';
+			} else {
+				return 'video';
+			}
+		},
+		// 判断是否是base64
+		isBase64(str) {
+			if (str === '' || typeof str !== 'string') return console.error('文件路径错误, base64', str);
+			return str.includes('blob:') || str.includes('data:image');
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.cl-updata {
+
+	.file-list {
+		display: grid;
+
+		&-row {
+			display: inline-flex;
+			align-items: center;
+			position: relative;
+			
+			.play-img {
+				width: 100%;
+			}
+
+			._image {
+				height: 100%;
+				width: 100%;
+			}
+
+			._video {
+				position: relative;
+				width: 100%;
+				height: 100%;
+				overflow: hidden;
+			}
+			
+			.video-fixed {
+				position: absolute;
+				top: 0;
+				left: 0;
+				bottom: 0;
+				width: 100%;
+				height: 100%;
+				border-radius: 10rpx;
+				z-index: 96;
+			}
+			
+			.play {
+				position: absolute;
+				top: 50%;
+				left: 50%;
+				transform: translate(-50%, -50%);
+				width: 30%;
+				z-index: 95;
+			}
+			
+			.app_play {
+				position: absolute;
+				top: 50%;
+				left: 50%;
+				transform: translate(-50%, -50%);
+				width: 50rpx;
+				height: 50rpx;
+			}
+
+			.remove {
+				position: absolute;
+				top: 0;
+				right: 0;
+				background-color: #373737;
+				height: 50rpx;
+				width: 50rpx;
+				border-bottom-left-radius: 200rpx;
+				z-index: 97;
+
+				.image {
+					width: 20rpx;
+					height: 20rpx;
+					position: absolute;
+					right: 12rpx;
+					top: 12rpx;
+				}
+			}
+		}
+
+		.add-image {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			border: 2rpx dashed #ccc;
+			width: 100%;
+			height: 100%;
+			border-radius: 5rpx;
+
+			&:active {
+				opacity: 0.8;
+			}
+
+			._image {
+				width: 40%;
+			}
+		}
+	}
+
+	.mask {
+		background-color: #000;
+		position: fixed;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		left: 0;
+		z-index: 99;
+
+		.block {
+			padding: 0 30rpx;
+			position: absolute;
+			top: 50%;
+			left: 50%;
+			transform: translate(-50%, -50%);
+			width: 100%;
+
+			.block_video {
+				width: 100%;
+				height: 78vh;
+			}
+		}
+
+		._root {
+			width: 60rpx;
+			height: 60rpx;
+			position: absolute;
+			left: 40rpx;
+			top: 5vh
+		}
+	}
+}
+</style>