Xiao_123 4 mesi fa
parent
commit
e7fdcdf68d

+ 1 - 0
components.d.ts

@@ -38,6 +38,7 @@ declare module 'vue' {
     IFrame: typeof import('./src/components/IFrame/index.vue')['default']
     Img: typeof import('./src/components/Upload/img.vue')['default']
     ImgCropper: typeof import('./src/components/ImgCropper/index.vue')['default']
+    Imgs: typeof import('./src/components/Upload/imgs.vue')['default']
     'Index copy': typeof import('./src/components/CtForm/index copy.vue')['default']
     IndustryTypeCard: typeof import('./src/components/industryTypeCard/index.vue')['default']
     Info: typeof import('./src/components/Enterprise/info.vue')['default']

+ 24 - 0
src/api/mall/user.js

@@ -26,4 +26,28 @@ export const receiveOrder = async (id) => {
       id
     }
   })
+}
+
+// 删除订单
+export const deleteTradeOrder = async (id) => {
+  return request.delete({
+    url: '/app-api/trade/order/delete',
+    params: { id }
+  })
+}
+
+// 取消订单
+export const cancelTradeOrder = async (id) => {
+  return request.delete({
+    url: '/app-api/trade/order/cancel',
+    params: { id }
+  })
+}
+
+// 创建单个商品评论
+export const createOrderItemComment = async (data) => {
+  return request.post({
+    url: '/app-api/trade/order/item/create-comment',
+    data
+  })
 }

+ 114 - 0
src/components/Upload/imgs.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class="mb-3 color-666">共<strong class="color-primary"> {{ srcList.length }} </strong>张,还能上传 <strong class="color-primary">{{ (Number(limit) - Number(srcList.length)) }}</strong> 张</div>
+  <div class="d-flex flex-wrap">
+    <div style="position: relative;" v-for="val in srcList" :key="val" class="mr-3 mb-3">
+      <v-icon color="error" class="close" @click="handleClose(val)">mdi-close-circle</v-icon>
+      <v-img :src="val" width="100" height="100" rounded class="imgBox"></v-img>
+    </div>
+    <div v-if="srcList.length < limit" class="upload d-flex align-center justify-center flex-column" @click="openFileInput">
+      <v-icon color="#ccc" :size="tips ? 30 : 50">mdi-plus</v-icon>
+      <div class="font-size-12 color-999">{{ tips }}</div>
+      <input
+        type="file"
+        ref="fileInput"
+        accept="image/*"
+        style="display: none;"
+        @change="handleUploadFile"
+      />
+    </div>
+  </div>
+</template>
+
+<script setup>
+// 图片组上传
+defineOptions({ name: 'upload-imgs'})
+import { ref, watch } from 'vue'
+import { uploadFile } from '@/api/common'
+import { useI18n } from '@/hooks/web/useI18n'
+import Snackbar from '@/plugins/snackbar'
+
+const props = defineProps({
+  modelValue: Array,
+  tips: {
+    type: String,
+    default: '上传图片'
+  },
+  showSnackbar: {
+    type: Boolean,
+    default: false
+  },
+  limit: {
+    type: [Number, String],
+    default: 9
+  }
+})
+
+const { t } = useI18n()
+const srcList = ref([])
+
+watch(() => props.modelValue, (newVal) => {
+  srcList.value = newVal
+}, { immediate: true }, { deep: true })
+
+// 选择文件
+const fileInput = ref()
+const clicked = ref(false)
+const openFileInput = () => {
+  if (srcList.value.length >= props.limit) return Snackbar.warning(`最多可上传${props.limit}张图片`) 
+  if (clicked.value) return
+  clicked.value = true
+  fileInput.value.click()
+  clicked.value = false
+}
+
+// 文件上传
+const accept = ['jpg', 'png', 'webp', 'jpeg']
+const handleUploadFile = async (e) => {
+  const file = e.target.files[0]
+  const size = file.size
+  if (size / (1024*1024) > 20) {
+    Snackbar.warning(t('common.fileSizeExceed'))
+    return
+  }
+
+  const fileType = file.name.split('.')[1]
+  if (!accept.includes(fileType)) return Snackbar.warning('请上传图片格式')
+
+  const formData = new FormData()
+  formData.append('file', file)
+  formData.append('path', 'img')
+  const { data } = await uploadFile(formData)
+  if (!data) return Snackbar.error('上传失败')
+  if (props.showSnackbar) Snackbar.success(t('common.uploadSucMsg'))
+  srcList.value.push(data)
+}
+
+const handleClose = (src) => {
+  const index = srcList.value.indexOf(src)
+  if (index === -1) return
+  srcList.value.splice(index, 1)
+}
+</script>
+
+<style scoped lang="scss">
+.upload {
+  width: 100px;
+  height: 100px;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  cursor: pointer;
+}
+.imgBox {
+  position: relative;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  padding: 5px;
+}
+.close {
+  position: absolute;
+  top: -10px;
+  right: -10px;
+  cursor: pointer;
+  z-index: 9;
+}
+</style>

+ 48 - 0
src/views/mall/components/GoodsItem/index.vue

@@ -0,0 +1,48 @@
+<template>
+  <div class="order-item-goods px-3 pt-3 cursor-pointer" @click="handleDetail(item)">
+    <div class="d-flex align-center">
+      <div style="width: 90px; height: 90px">
+        <v-img :src="item.picUrl"></v-img>
+      </div>
+      <div class="ml-5">
+        <p class="font-size-15" :class="{'goods-name': showHover}">{{ item.spuName }}</p>
+        <p class="color-999 font-size-14">{{ item.properties.map((property) => property.valueName).join(' ') }}</p>
+        <p>
+          <span class="color-333">¥{{ fen2yuan(item.price) }}</span>
+          <span v-if="item.count" class="color-999 font-size-13 ml-1">x {{ item.count }}</span>
+        </p>
+      </div>
+    </div>
+    <v-divider v-if="showLine" class="mt-3"></v-divider>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'mall-goods-item'})
+import { fen2yuan } from '@/hooks/web/useGoods'
+
+defineProps({ 
+  item: {
+    type: Object,
+    default: () => ({})
+  },
+  showLine: {
+    type: Boolean,
+    default: true
+  },
+  showHover: {
+    type: Boolean,
+    default: true
+  }
+})
+
+const handleDetail = ({ id }) => {
+  window.open(`/mall/user/order/detail/${id}`)
+}
+</script>
+
+<style scoped lang="scss">
+.goods-name:hover {
+  color: var(--v-primary-base);
+}
+</style>

+ 52 - 0
src/views/mall/user/order/commentForm.vue

@@ -0,0 +1,52 @@
+<template>
+  <div v-for="(val, index) in orderList" :key="val.id">
+    <GoodsItem :item="val" :showHover="false" :showLine="false" />
+    <div class="d-flex align-center my-3">
+      <span>商品质量</span>
+      <v-rating :length="5" :size="34" v-model="commentList[index].descriptionScores" color="warning" active-color="warning" />
+    </div>
+    <div class="d-flex align-center my-3">
+      <span>服务态度</span>
+      <v-rating :length="5" :size="34" v-model="commentList[index].benefitScores" color="warning" active-color="warning" />
+    </div>
+    <v-textarea v-model="commentList[index].content" rows="3" color="primary" clearable counter label="宝贝满足你的期待吗?说说你的使用心得,分享给想买的他们吧~" />
+    <Imgs v-model="commentList[index].picUrls" limit="6"></Imgs>
+    <v-checkbox v-model="commentList[index].anonymous" color="primary" hide-details label="匿名评论"></v-checkbox>
+    <v-divider v-if="index !== orderList.length - 1" color="error" class="my-3"></v-divider>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'mall-user-order-commentForm' })
+import { ref, onMounted } from 'vue'
+import { getMallOrderDetail } from '@/api/mall/user'
+import Snackbar from '@/plugins/snackbar'
+import GoodsItem from '../../components/GoodsItem/index.vue'
+
+const props = defineProps({ orderId: [String, Number] })
+
+const commentList = ref([])
+const orderList = ref([])
+
+onMounted(async () => {
+  if (!props.orderId) return Snackbar.warning('请重新选择订单')
+  const data = await getMallOrderDetail(props.orderId)
+  orderList.value = data.items
+  commentList.value = data.items.map(e => {
+    return {
+      orderItemId: e.id,
+      anonymous: false,
+      benefitScores: 5,
+      descriptionScores: 5,
+      picUrls: [],
+      content: ''
+    }
+  })
+})
+
+defineExpose({ commentList })
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 64 - 22
src/views/mall/user/order/index.vue

@@ -14,30 +14,17 @@
             {{ formatOrderStatus(val) }}
           </div>
         </div>
-        <div v-for="k in val.items" :key="k.id" class="order-item-goods px-3 pt-3 cursor-pointer" @click="handleDetail(val)">
-          <div class="d-flex align-center">
-            <div style="width: 90px; height: 90px">
-              <v-img :src="k.picUrl"></v-img>
-            </div>
-            <div class="ml-5">
-              <p class="font-size-15">{{ k.spuName }}</p>
-              <p class="color-999 font-size-14">{{ k.properties.map((property) => property.valueName).join(' ') }}</p>
-              <p>
-                <span class="color-333">¥{{ fen2yuan(k.price) }}</span>
-                <span v-if="k.count" class="color-999 font-size-13 ml-1">x {{ k.count }}</span>
-              </p>
-            </div>
-          </div>
-          <v-divider class="mt-3"></v-divider>
-        </div>
+        <!-- 商品列表 -->
+        <GoodsItem v-for="k in val.items" :key="k.id" :item="k" />
+        <!-- 操作按钮 -->
         <div class="text-end pa-3 font-size-13 color-666">
           <div>共{{ val.productCount }}件商品,合计:¥{{ fen2yuan(val.payPrice) }}</div>
           <v-btn v-if="val.buttons.length === 0" class="mt-2" variant="tonal" rounded="xl" @click.stop="handleDetail(val)">查看详情</v-btn>
           <v-btn v-if="val.buttons.includes('confirm')" class="mt-2" variant="tonal" color="success" rounded="xl" @click.stop="handleConfirm(val)">确认收货</v-btn>
-          <v-btn v-if="val.buttons.includes('comment')" class="mt-2" variant="tonal" rounded="xl">评价</v-btn>
+          <v-btn v-if="val.buttons.includes('comment')" class="mt-2" variant="tonal" rounded="xl" @click.stop="handleComment(val)">评价</v-btn>
           <v-btn v-if="val.buttons.includes('express')" class="mt-2" variant="tonal" rounded="xl">查看物流</v-btn>
-          <v-btn v-if="val.buttons.includes('cancel')" class="mt-2" variant="tonal" rounded="xl">取消订单</v-btn>
-          <v-btn v-if="val.buttons.includes('delete')" class="mt-2" variant="tonal" color="error" rounded="xl">删除订单</v-btn>
+          <v-btn v-if="val.buttons.includes('cancel')" class="mt-2" variant="tonal" rounded="xl" @click.stop="handleCancel(val)">取消订单</v-btn>
+          <v-btn v-if="val.buttons.includes('delete')" class="mt-2" variant="tonal" color="error" rounded="xl" @click.stop="handleDelete(val)">删除订单</v-btn>
           <v-btn v-if="val.buttons.includes('pay')" class="mt-2 ml-3" variant="tonal" rounded="xl">继续支付</v-btn>
         </div>
       </div>
@@ -45,19 +32,25 @@
     </div>
     <Empty v-else :elevation="false" :message="tab === -1 ? '暂无订单' : '暂无' + tabList.find(e => e.value === tab).title + '订单'"></Empty>
   </div>
+
+  <CtDialog :visible="showDialog" titleClass="text-h6" :footer="true" :widthType="3" title="商品评论" @submit="handleSubmit" @close="handleClose">
+    <CommentForm ref="commentFormRef" v-if="showDialog" :orderId="commentOrderId" />
+  </CtDialog>
 </template>
 
 <script setup>
 defineOptions({ name: 'mall-user-order-index'})
 import { ref } from 'vue'
-import { getMallOrderPage, receiveOrder } from '@/api/mall/user'
+import { getMallOrderPage, receiveOrder, deleteTradeOrder, cancelTradeOrder, createOrderItemComment } from '@/api/mall/user'
 import { timesTampChange } from '@/utils/date'
 import { fen2yuan, handleOrderButtons, formatOrderStatus, formatOrderColor } from '@/hooks/web/useGoods'
 import Confirm from '@/plugins/confirm'
 import Snackbar from '@/plugins/snackbar'
-import { useRouter } from 'vue-router'
+import GoodsItem from '../../components/GoodsItem/index.vue'
+import CommentForm from './commentForm.vue'
+// import { useRouter } from 'vue-router'
 
-const router = useRouter()
+// const router = useRouter()
 const tab = ref(-1)
 const tabList = [
   { title: '全部', value: -1 },
@@ -99,6 +92,53 @@ const handleDetail = ({ id }) => {
   window.open(`/mall/user/order/detail/${id}`)
 }
 
+// 删除订单
+const handleDelete = async ({ id }) => {
+  Confirm('系统提示', '是否确认删除该订单?').then(async () => {
+    await deleteTradeOrder(id)
+    Snackbar.success('删除成功')
+    await getOrderPage()
+  })
+}
+
+// 取消订单
+const handleCancel = async ({ id }) => {
+  Confirm('系统提示', '是否确认取消该订单?').then(async () => {
+    await cancelTradeOrder(id)
+    Snackbar.success('取消成功')
+    await getOrderPage()
+  })
+}
+
+// 商品评论
+const showDialog = ref(false)
+const commentOrderId = ref(null) // 订单id
+const commentFormRef = ref()
+const handleComment = (val) => {
+  commentOrderId.value = val.id
+  showDialog.value = true
+}
+const handleClose = () => {
+  commentOrderId.value = null
+  showDialog.value = false
+}
+const handleSubmit = async () => {
+  const commentList = commentFormRef.value.commentList
+  for (const comment of commentList) {
+    await createOrderItemComment(comment)
+  }
+  Snackbar.success('评论成功')
+  handleClose()
+  getOrderPage()
+}
+
+// 收货成功后提示去评论
+const handlePromptComment = (id) => {
+  Confirm('确认收货成功', '是否前往评价?', { sureText: '立即评价', cancelText: '关闭' }).then(() => {
+    handleComment({ id })
+  })
+}
+
 // 确认收货
 const handleConfirm = ({ id }) => {
   if (!id) return
@@ -107,8 +147,10 @@ const handleConfirm = ({ id }) => {
     Snackbar.success('收货成功')
     queryParams.value.pageNo = 1
     await getOrderPage()
+    handlePromptComment(id) // 收货成功后提示去评论
   })
 }
+
 </script>
 
 <style scoped lang="scss">