Browse Source

2024-12-25

cmy 10 months ago
parent
commit
bc3e05640a

+ 1 - 1
.env.dev

@@ -20,7 +20,7 @@ VITE_SOURCEMAP=true
 VITE_OUT_DIR=dist-dev
 
 # 标题
-VITE_APP_TITLE=ElementAdmin
+VITE_APP_TITLE=七牛数字
 
 # 是否包分析
 VITE_USE_BUNDLE_ANALYZER=false

+ 41 - 4
src/api/goods/index.ts

@@ -1,6 +1,6 @@
 import request from '@/axios'
 import { REQUEST_BASE } from '@/constants'
-import { categoryData, goodsData } from './types'
+import { categoryData, goodsData, goodsSearch, auditData } from './types'
 import { productDataParse } from './parseData'
 export const getProductCategory = (params: {
   page?: number
@@ -18,20 +18,57 @@ export const delProductCategory = (id: number): Promise<IResponse> => {
 export const putProductCategory = (data: categoryData): Promise<IResponse> => {
   return request.post({ url: `${REQUEST_BASE}/productCategory/update/${data.id}`, data })
 }
-export const getProduct = (params: { page?: number; limit?: number }): Promise<IResponse> => {
+export const getProduct = (params: goodsSearch): Promise<IResponse> => {
   return request.get({ url: `${REQUEST_BASE}/product/index`, params })
 }
 export const addProduct = (data: goodsData): Promise<IResponse> => {
   const pushData = productDataParse(data)
   return request.post({ url: `${REQUEST_BASE}/product/save`, data: pushData })
 }
-export const delProduct = (id: number): Promise<IResponse> => {
-  return request.post({ url: `${REQUEST_BASE}/product/delete/${id}` })
+export const delProduct = (id: number, is_del: 0 | 1): Promise<IResponse> => {
+  return request.post({ url: `${REQUEST_BASE}/product/delete/${id}`, data: { is_del } })
 }
 export const putProduct = (data: goodsData): Promise<IResponse> => {
   const pushData = productDataParse(data)
   return request.post({ url: `${REQUEST_BASE}/product/update/${data.id}`, data: pushData })
 }
+export const postProductStatus = (id: number, status: number): Promise<IResponse> => {
+  return request.post({
+    url: `${REQUEST_BASE}/product/status/${id}`,
+    data: {
+      status
+    }
+  })
+}
+
+/**
+ * 提交产品审核请求
+ *
+ * @param data 包含产品审核信息的对象,包括产品的验证状态和拒绝原因
+ * @returns 返回一个Promise,解析为API响应对象
+ *
+ * 此函数向服务器发送一个POST请求,以审核产品信息它需要审核数据对象,
+ * 其中包含产品的验证状态(is_verify)和如果产品被拒绝的原因(refusal)请求的URL
+ * 是动态构建的,包含产品ID
+ */
+export const postProductAudit = (data: auditData): Promise<IResponse> => {
+  return request.post({
+    url: `${REQUEST_BASE}/product/audit/${data.id}`,
+    data: {
+      is_verify: data.is_verify,
+      refusal: data.refusal
+    }
+  })
+}
+/**
+ * 获取产品详细信息
+ *
+ * 此函数通过发送GET请求来获取指定产品的详细信息,包括商店信息和产品价值等数据
+ * 它接受一个产品ID作为参数,并返回一个Promise对象,该对象解析为包含产品详细信息的响应
+ *
+ * @param id 产品的唯一标识符,用于指定需要获取详细信息的产品
+ * @returns 返回一个Promise对象,解析为接口IResponse的实现,包含商品信息和产品价值数组
+ */
 export const getProductDetail = (
   id: any
 ): Promise<

+ 17 - 1
src/api/goods/types.ts

@@ -19,6 +19,7 @@ export interface attrsValue {
   cost: number //商品成本价
 }
 export interface goodsData {
+  store_id: number | string
   id?: number
   name: string //商品名称
   video_open: 0 | 1 | true | false //商品视频是否开启:0关闭 1开启
@@ -27,7 +28,7 @@ export interface goodsData {
   slider_image: string[] //轮播图
   info?: string //简介
   keyword?: string[] | string //关键词
-  cate_ids: string[] | string //分类
+  cate_ids: number[] //分类
   postage: number //上楼费
   temp_id?: string //上楼费模板ID
   unit_name: string //单位
@@ -40,3 +41,18 @@ export interface goodsData {
   sort: number //排序
   description: string //商品内容
 }
+export interface goodsSearch {
+  name?: string
+  page?: number
+  limit?: number
+  cate_ids?: number | ''
+  store_id?: number | ''
+  is_del?: 'all' | '0' | '1' //是否删除:0否1是
+  is_show?: 'all' | '0' | '1' //状态:0=未上架,1=上架
+  is_verify?: 'all' | '0' | '1' | '-1' | '-2' //审核状态:-2=强制下架,-1=未通过,0=待审核,1=通过
+}
+export interface auditData {
+  id: number
+  is_verify: -1 | 1 //审核状态:-1=拒绝,1=通过
+  refusal: string //未通过原因
+}

+ 34 - 1
src/components/Editor/src/Editor.vue

@@ -55,6 +55,8 @@ const handleCreated = (editor: IDomEditor) => {
 const toolbarConfig = {
   excludeKeys: ['group-video']
 }
+const loading = ref(false)
+const loadingText = ref('上传中')
 // 编辑器配置
 const editorConfig = computed((): IEditorConfig => {
   return Object.assign(
@@ -71,8 +73,36 @@ const editorConfig = computed((): IEditorConfig => {
           allowedFileTypes: ['image/*'],
           server: `${REQUEST_BASE}/file/upload/0`,
           customInsert(res: any, insertFn) {
-            // console.log(res, 'res')
             insertFn(res.data.src, res.data.src, res.data.src)
+          },
+          // 上传之前触发
+          onBeforeUpload(file: File) {
+            loading.value = true
+            return file
+          },
+          // 上传进度的回调函数
+          onProgress(progress: number) {
+            loadingText.value = `进度${progress}%`
+            console.log('progress', progress)
+          },
+          // 单个文件上传成功之后
+          onSuccess(file: File, res: any) {
+            loading.value = false
+            console.log(`${file.name} 上传成功`, res)
+          },
+          // 单个文件上传失败
+          onFailed(file: File, res: any) {
+            loading.value = false
+            console.log(`${file.name} 上传失败`, res)
+          },
+          onError(_: File, err: any, __: any) {
+            loading.value = false
+            //判断是否图片上传过大
+            if (err.message.indexOf('exceeds maximum allowed size of') > -1) {
+              ElMessage.error(
+                `文件上传不可大于${err.message.split('exceeds maximum allowed size of')[1]}`
+              )
+            }
           }
         }
       },
@@ -144,6 +174,9 @@ defineExpose({
     />
     <!-- 编辑器 -->
     <Editor
+      v-loading="loading"
+      :element-loading-text="loadingText"
+      element-loading-background="rgba(255, 255, 255, 0.8)"
       v-model="valueHtml"
       :editorId="editorId"
       :defaultConfig="editorConfig"

+ 22 - 8
src/components/UpFile/src/components/upFile.vue

@@ -75,7 +75,8 @@ const form = reactive({
   type: '1', //保存类型1文件2链接地址
   fileList: []
 })
-const upload = ref<UploadInstance>()
+const uploadImage = ref<UploadInstance>()
+const uploadVideo = ref<UploadInstance>()
 const saveLoading = ref(false)
 const uploadFileNum = ref(0)
 const confirm = async () => {
@@ -83,7 +84,11 @@ const confirm = async () => {
   saveLoading.value = true
   // 判断是否本地上传
   if (form.type == '1') {
-    upload.value!.submit()
+    if (props.fileType == 1) {
+      uploadImage.value!.submit()
+    } else if (props.fileType == 2) {
+      uploadVideo.value!.submit()
+    }
     return
   }
   if (form.type == '2') {
@@ -161,9 +166,9 @@ const successUpLoad = () => {
         <el-form-item label="链接地址:" v-if="form.type == '2'">
           <ElInput v-model="form.images" placeholder="请输入链接地址" />
         </el-form-item>
-        <el-form-item label="上传图片:" v-show="form.type == '1'">
+        <el-form-item label="上传图片:" v-show="form.type == '1' && fileType == 1">
           <ElUpload
-            ref="upload"
+            ref="uploadImage"
             class="mt-10px"
             @success="successUpLoad"
             multiple
@@ -187,19 +192,28 @@ const successUpLoad = () => {
             </template>
           </ElUpload>
         </el-form-item>
-        <!-- <el-form-item label="上传图片:" v-if="form.type == '1'">
+        <el-form-item label="上传视频:" v-if="form.type == '1' && fileType == 2">
           <ElUpload
-            :action="`${REQUEST_BASE}/file/video_upload`"
+            ref="uploadVideo"
+            :action="`${REQUEST_BASE}/file/upload/0`"
             multiple
             v-model:file-list="form.fileList"
-            accept="audio/mpeg,.wma,.wav, video/mp4,.amr,.key,.xlsx,.xls,.apk,application/pdf"
+            @success="successUpLoad"
+            accept="audio/mpeg,.wma,.wav, video/mp4"
             :auto-upload="false"
+            :data="{
+              pid: form.pid
+            }"
+            :headers="{
+              'Authori-Zation': userStore.getTokenKey + userStore.getToken
+            }"
           >
+            <BaseButton>上传视频</BaseButton>
             <template #tip>
               <div class="el-upload__tip">仅支持mp4、mpeg格式</div>
             </template>
           </ElUpload>
-        </el-form-item> -->
+        </el-form-item>
       </ElForm>
     </template>
     <template #footer>

+ 24 - 12
src/components/UpFile/src/index.vue

@@ -13,7 +13,9 @@ import {
   ElTooltip,
   ElEmpty,
   ElMessageBox,
-  ElMessage
+  ElMessage,
+  ElTabs,
+  ElTabPane
 } from 'element-plus'
 import { Search } from '@/components/Search'
 import { BaseButton } from '@/components/Button'
@@ -111,12 +113,13 @@ const currentChange = (data) => {
   currentPage.value = 1
   getList()
 }
-// const tabChange = async (data) => {
-//   console.log('tabChange', data)
-//   fileType.value = data
-//   currentPage.value = 1
-//   fetchDepartment()
-// }
+const tabChange = async (data) => {
+  console.log('tabChange', data)
+  fileType.value = data
+  currentPage.value = 1
+  dataList.value = []
+  fetchDepartment()
+}
 
 const filterNode = (value: string, data) => {
   if (!value) return true
@@ -281,10 +284,10 @@ const delData = async (id?: string) => {
       </ElTree>
     </ContentWrap>
     <ContentWrap class="flex-[3] ml-20px">
-      <!-- <ElTabs class="tabs" v-model="fileType" @tab-change="tabChange">
-        <el-tab-pane label="图片" :name="1" />
-        <el-tab-pane label="视频" :name="2" />
-      </ElTabs> -->
+      <ElTabs class="tabs" v-model="fileType" @tab-change="tabChange">
+        <ElTabPane label="图片" :name="1" />
+        <ElTabPane label="视频" :name="2" />
+      </ElTabs>
       <Search :schema="searchSchema" @reset="setSearchParams" @search="setSearchParams" />
       <div class="mb-10px">
         <BaseButton type="primary" :icon="icon" @click="AddAction">{{
@@ -312,8 +315,9 @@ const delData = async (id?: string) => {
             </ElTooltip>
           </template>
           <ElImage
+            v-if="fileType == 1"
             class="card-list-img"
-            fit="cover"
+            fit="contain"
             :key="index"
             :src="item.satt_dir"
             hide-on-click-modal
@@ -326,6 +330,14 @@ const delData = async (id?: string) => {
               </div>
             </template>
           </ElImage>
+          <video
+            preload="none"
+            v-if="fileType == 2"
+            controls
+            class="card-list-img"
+            :src="item.satt_dir"
+          >
+          </video>
           <template #footer>
             <BaseButton
               class="buttom-checked"

+ 37 - 3
src/components/UpFile/src/upImageButtom.vue

@@ -62,14 +62,48 @@ watch(showImage, (val) => {
     actionData.value = []
   }
 })
+
+// 移动图片
+const dragging = ref<string>()
+const handleDragStart = (item) => {
+  console.log(item, 'item')
+  dragging.value = item
+}
+const handleDragEnd = () => {
+  dragging.value = ''
+}
+const handleDragOver = (e) => {
+  e.dataTransfer.dropEffect = 'move'
+}
+const handleDragEnter = (e, item) => {
+  e.dataTransfer.effectAllowed = 'move'
+  if (item === dragging.value || !dragging.value) {
+    return
+  }
+  const newItems = [...modelValue.value]
+  const src = newItems.indexOf(dragging.value)
+  console.log(src, 'src')
+  const dst = newItems.indexOf(item)
+  newItems.splice(dst, 0, ...newItems.splice(src, 1))
+  modelValue.value = newItems
+}
 </script>
 
 <template>
   <div class="w-full h-full">
     <div class="flex">
       <template v-if="modelValue.length > 0">
-        <div class="imageItem" v-for="(item, index) in modelValue" :key="index">
-          <ElImage :src="item" class="image" fit="cover">
+        <div
+          class="imageItem"
+          v-for="(item, index) in modelValue"
+          :key="index"
+          draggable="true"
+          @dragstart="handleDragStart(item)"
+          @dragover.prevent="handleDragOver($event)"
+          @dragenter="handleDragEnter($event, item)"
+          @dragend="handleDragEnd()"
+        >
+          <ElImage :src="item" class="image" fit="contain">
             <template #error>
               <div class="flex items-center justify-center bg-gray-200 w-full h-full">
                 <Icon icon="vi-ep:picture" />
@@ -95,7 +129,7 @@ watch(showImage, (val) => {
       </div>
     </div>
     <div v-if="tip" class="text-xs text-gray-400 mt-5px">{{ tip }}</div>
-    <Dialog maxHeight="630px" v-model="showImage" title="选择图片" width="1190px">
+    <Dialog maxHeight="670px" v-model="showImage" title="选择图片" width="1190px">
       <upFile v-model="actionData" />
       <template #footer>
         <BaseButton type="primary" :loading="saveLoading" @click="save"> 确定 </BaseButton>

+ 122 - 62
src/views/Goods/edit/add.vue

@@ -12,19 +12,23 @@ import {
   ElFormItem,
   ElInputTag,
   ElMessage,
-  ElMessageBox
+  ElMessageBox,
+  ElSelect,
+  ElOption
 } from 'element-plus'
 import { Editor, EditorExpose } from '@/components/Editor'
-// import { IDomEditor } from '@wangeditor/editor'
 import { useRoute, useRouter } from 'vue-router'
 import { goodsData, attrsValue } from '@/api/goods/types'
 import { getProductCategory, addProduct, getProductDetail, putProduct } from '@/api/goods'
 import { UpImgButtom } from '@/components/UpFile'
+import { getStoreList } from '@/api/store'
+import { isArray } from 'lodash-es'
 const pageTitle = ref('添加')
 const { params } = useRoute()
 const { push } = useRouter()
 const actinoTabIndex = ref(1)
 const formData = reactive<goodsData & { attrs_value: attrsValue[]; keyword: string[] }>({
+  store_id: '',
   id: 0,
   name: '',
   video_open: 0,
@@ -58,52 +62,6 @@ const attrs_value = reactive<
   ot_price: 0,
   cost: 0
 })
-
-const loadingData = ref(false)
-onMounted(async () => {
-  if (params.type == 'add') {
-    pageTitle.value = '添加商品'
-  } else if (params.type == 'edit') {
-    pageTitle.value = '编辑商品'
-    try {
-      const res = await getProductDetail(params.id)
-      const data = res.data.storeInfo
-      // console.log(data.cate_ids, 'res')
-      formData.id = data.id
-      formData.name = data.name
-      formData.video_open = data.video_open
-      formData.video_link = data.video_link
-      formData.slider_image = data.slider_image
-      formData.image = data.image
-      formData.keyword = data.keyword.split(',')
-      formData.cate_ids = data.cate_ids.map((re) => parseInt(re))
-      formData.postage = data.postage
-      formData.temp_id = data.temp_id
-      formData.unit_name = data.unit_name
-      formData.sales = data.sales
-      formData.ficti = data.ficti
-      formData.spec_type = data.spec_type
-      formData.is_show = data.is_show
-      formData.attrs = data.attrs
-      formData.attrs_value = res.data.productValue.map((e) => e)
-      formData.sort = data.sort
-      formData.description = data.description
-      // 判断是否单规格
-      if (formData.spec_type == 0) {
-        const values = res.data.productValue[0]
-        // console.log(values, 'values')
-        attrs_value.cost = values.cost
-        attrs_value.price = values.price
-        attrs_value.ot_price = values.ot_price
-        attrs_value.stock = values.stock
-        attrs_value.suk = values.suk
-        attrs_value.image = [values.image]
-      }
-    } catch (error) {
-      console.log(error, 'error')
-    }
-  }
-})
 const validateField = (field, errorMessage) => {
   return {
     message: errorMessage,
@@ -135,9 +93,10 @@ const getRulesForSpecType = (specType) => {
 }
 
 const rules = reactive({
+  store_id: [{ required: true, message: '请选择门店', trigger: 'change' }],
   cate_ids: [{ required: true, message: '请选择商品分类', trigger: 'change' }],
   unit_name: [{ required: true, message: '请填写商品单位', trigger: 'change' }],
-  name: [{ required: true, message: '请输入活动名称', trigger: 'blur' }],
+  name: [{ required: true, message: '请输入商品名称', trigger: 'blur' }],
   image: [{ required: true, message: '请选择商品主图', trigger: 'change' }],
   slider_image: [
     {
@@ -145,7 +104,7 @@ const rules = reactive({
       trigger: 'change',
       required: true,
       validator: (_, val, callback) => {
-        // console.log(val, formData.slider_image, 'val')
+        console.log(val, formData.slider_image, 'val')
         if (!val || val.length === 0) {
           callback(new Error('请选择轮播图片'))
         } else {
@@ -159,8 +118,8 @@ const rules = reactive({
 const formRef = ref<FormInstance>()
 
 const cateIdsProps = {
-  // checkStrictly: true,
-  emitPath: false,
+  checkStrictly: false,
+  // emitPath: false,
   multiple: true,
   lazy: true,
   lazyLoad: (node, resolve) => {
@@ -183,14 +142,24 @@ const cateIdsProps = {
   }
 }
 
-// const changeHtml = (editor: IDomEditor) => {
-//   console.log(formData.description)
-// }
-
 const editorRef = ref<typeof Editor & EditorExpose>()
+const flattenCateIds = (items: (number | number[])[]): Set<number> => {
+  const result = new Set<number>()
+
+  const flatten = (item: number | number[]) => {
+    if (isArray(item)) {
+      item.forEach(flatten)
+    } else {
+      result.add(item)
+    }
+  }
+
+  items.forEach(flatten)
 
+  return result
+}
 const save = async (formEl: FormInstance | undefined) => {
-  if (!formEl) return
+  if (!formEl) return []
   const valid = await formEl.validate((valid, fields) => {
     if (valid) {
       console.log('submit!')
@@ -200,12 +169,15 @@ const save = async (formEl: FormInstance | undefined) => {
         ElMessage.error(fields[key][0].message)
         return
       }
-      // console.log('error submit!', fields)
+      console.log('error submit!', fields)
     }
   })
   // console.log(valid, 'params')
   if (valid) {
+    const cateIds: number[] = Array.from(flattenCateIds(formData.cate_ids)).map((re) => re)
+    console.log(cateIds, 'cateIds')
     const data: goodsData = {
+      store_id: formData.store_id,
       name: formData.name,
       video_open: formData.video_open,
       video_link: formData.video_link,
@@ -213,7 +185,7 @@ const save = async (formEl: FormInstance | undefined) => {
       info: formData.info,
       image: formData.image,
       keyword: formData.keyword,
-      cate_ids: formData.cate_ids,
+      cate_ids: cateIds,
       postage: formData.postage,
       temp_id: formData.temp_id,
       unit_name: formData.unit_name,
@@ -222,7 +194,7 @@ const save = async (formEl: FormInstance | undefined) => {
       spec_type: formData.spec_type,
       attrs: formData.attrs,
       attrs_value: formData.attrs_value,
-      description: formData.name,
+      description: formData.description,
       sort: formData.sort,
       is_show: formData.is_show
     }
@@ -231,7 +203,7 @@ const save = async (formEl: FormInstance | undefined) => {
     }
     try {
       let res = {}
-      // console.log(params.type == 'add', '11111')
+      console.log(params.type == 'add', '11111')
       if (params.type == 'add') {
         res = await addProduct(data)
       } else if (params.type == 'edit') {
@@ -244,7 +216,7 @@ const save = async (formEl: FormInstance | undefined) => {
           title = '编辑'
         }
         ElMessage.success('保存成功')
-        ElMessageBox.confirm(`${title}成功是否返回商列表`, '提示', {
+        ElMessageBox.confirm(`${title}成功是否返回商列表`, '提示', {
           confirmButtonText: '返回列表',
           cancelButtonText: `继续${title}`,
           type: 'warning'
@@ -260,6 +232,52 @@ const save = async (formEl: FormInstance | undefined) => {
   }
 }
 
+const loadingData = ref(false)
+onMounted(async () => {
+  if (params.type == 'add') {
+    pageTitle.value = '添加商品'
+  } else if (params.type == 'edit') {
+    pageTitle.value = '编辑商品'
+    try {
+      const res = await getProductDetail(params.id)
+      const data = res.data.storeInfo
+      formData.id = data.id
+      formData.store_id = data.store_id
+      formData.name = data.name
+      formData.video_open = data.video_open
+      formData.video_link = data.video_link
+      formData.slider_image = data.slider_image
+      formData.image = data.image
+      formData.keyword = data.keyword.split(',')
+      formData.cate_ids = data.cate_ids.map((re) => parseInt(re))
+      formData.postage = data.postage
+      formData.temp_id = data.temp_id
+      formData.unit_name = data.unit_name
+      formData.sales = data.sales
+      formData.ficti = data.ficti
+      formData.spec_type = data.spec_type
+      formData.is_show = data.is_show
+      formData.attrs = data.attrs
+      formData.attrs_value = res.data.productValue.map((e) => e)
+      formData.sort = data.sort
+      formData.description = data.description
+      // 判断是否单规格
+      if (formData.spec_type == 0) {
+        const values = res.data.productValue[0]
+        // console.log(values, 'values')
+        attrs_value.cost = values.cost
+        attrs_value.price = values.price
+        attrs_value.ot_price = values.ot_price
+        attrs_value.stock = values.stock
+        attrs_value.suk = values.suk
+        attrs_value.image = [values.image]
+      }
+      getStore('', data.store_id)
+    } catch (error) {
+      console.log(error, 'error')
+    }
+  }
+})
 const backList = () => {
   push('/goods/list')
 }
@@ -289,6 +307,28 @@ const backList = () => {
 //   attrs_value.suk = '默认'
 //   attrs_value.image = []
 // }
+const storeLoading = ref(false)
+const storeOptions = ref<any[]>([])
+const getStore = async (query = '', id = '') => {
+  try {
+    storeLoading.value = true
+    const res = await getStoreList({ page: 1, limit: 10, name: query, id })
+    if (res) {
+      storeOptions.value = res.data.list.map((item) => {
+        return {
+          value: item.id,
+          label: item.name
+        }
+      })
+    } else {
+      return []
+    }
+  } catch (error) {
+    console.log(error)
+  } finally {
+    storeLoading.value = false
+  }
+}
 </script>
 <template>
   <ContentWrap
@@ -317,6 +357,26 @@ const backList = () => {
               placeholder="请选择商品分类"
             />
           </ElFormItem>
+          <ElFormItem label="门店" prop="store_id">
+            <ElSelect
+              filterable
+              remote
+              reserve-keyword
+              :disabled="params.type == 'edit'"
+              v-model="formData.store_id"
+              :remote-method="getStore"
+              :loading="storeLoading"
+              remote-show-suffix
+              placeholder="请选择门店"
+            >
+              <ElOption
+                v-for="item in storeOptions"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
+              />
+            </ElSelect>
+          </ElFormItem>
           <ElFormItem label="商品名称" prop="name">
             <ElInput v-model="formData.name" placeholder="请输入商品名称" />
           </ElFormItem>

+ 370 - 48
src/views/Goods/list/index.vue

@@ -1,13 +1,21 @@
 <script setup lang="tsx">
 import { onMounted, reactive, ref, unref, watch } from 'vue'
-import { getProduct, getProductCategory } from '@/api/goods'
+import {
+  delProduct,
+  getProduct,
+  getProductCategory,
+  postProductStatus,
+  postProductAudit
+} from '@/api/goods'
+import { getStoreList } from '@/api/store'
 import { useI18n } from '@/hooks/web/useI18n'
 import { Search } from '@/components/Search'
 import { FormSchema } from '@/components/Form'
 import { ContentWrap } from '@/components/ContentWrap'
 import { BaseButton } from '@/components/Button'
-import { userSearch } from '@/api/user/types'
+import { goodsSearch } from '@/api/goods/types'
 import {
+  ElMessageBox,
   ElSwitch,
   ElDivider,
   ElDropdown,
@@ -15,48 +23,240 @@ import {
   ElDropdownItem,
   ElTable,
   ElTableColumn,
-  ElPagination
+  ElPagination,
+  ElMessage,
+  ElTabs,
+  ElTabPane,
+  ElText
 } from 'element-plus'
-import { searchTime } from '@/utils/searchTime'
+import type { Action } from 'element-plus'
 import { TableImage } from '@/components/tableImage'
 import BatchSet from './components/BatchSet.vue'
 import { useSearch } from '@/hooks/web/useSearch'
 import Icon from '@/components/Icon/src/Icon.vue'
 import { useRouter } from 'vue-router'
 const { push } = useRouter()
-const { searchRegister } = useSearch()
+const { searchRegister, searchMethods } = useSearch()
+const { setSchema, setValues, getFormData } = searchMethods
 const { t } = useI18n()
+// tab切换查询参数
+const tabsConfig = reactive({
+  up: {
+    title: '出售中商品',
+    total: 0,
+    search: { is_show: '1', is_del: '0', is_verify: '1' }
+  }, //出售中商品
+  dom: {
+    title: '仓库中商品',
+    total: 0,
+    search: { is_show: '0', is_del: '0', is_verify: '1' }
+  }, //仓库中商品
+  examine: {
+    title: '待审核商品',
+    total: 0,
+    search: { is_show: 'all', is_del: '0', is_verify: '0' }
+  }, //待审核商品
+  trash: {
+    title: '商品回收站',
+    total: 0,
+    search: { is_show: 'all', is_del: '1', is_verify: 'all' }
+  }, //商品回收站
+  all: {
+    title: '高级查询',
+    total: 0,
+    search: { is_show: 'all', is_del: 'all', is_verify: 'all' }
+  } //高级查询
+})
 const currentPage = ref(1)
 const pageSize = ref(10)
 const total = ref(0)
 const dataList = ref<any[]>([])
 const loading = ref(false)
-const getList = async () => {
-  loading.value = true
-  const { data } = await getProduct({
-    page: unref(currentPage) || 1,
-    limit: unref(pageSize) || 10,
-    ...unref(searchParams)
-  })
-  loading.value = false
-  dataList.value = data.list.map((item) => {
-    item.is_show_bol = item.is_show == 1 ? true : false
-    return item
-  })
-  total.value = data.count
-}
+
 onMounted(async () => {
   try {
+    getTabNumber('examine') //待审核商品数量
+    getTabNumber('dom') //仓鼠中商品数量
+    getTabNumber('trash') //商品回收站
     getList()
+    getStroe()
   } catch (error) {
     console.error('Failed to fetch data:', error)
     // 可以在这里添加更详细的错误处理逻辑,例如显示错误信息给用户
   }
 })
+const getList = async () => {
+  try {
+    loading.value = true
+    // 提取 unref 操作以减少冗余
+    const page = unref(currentPage) || 1
+    const limit = unref(pageSize) || 10
+    const searchParamsValue = unref(searchParams)
+    const { data } = await getProduct({
+      page,
+      limit,
+      ...searchParamsValue
+    })
+    // 处理边界条件
+    if (data && data.list && data.count !== undefined) {
+      dataList.value = data.list.map((item) => {
+        item.is_show_bol = Boolean(item.is_show)
+        return item
+      })
+      total.value = data.count
+      tabsConfig[activeName.value].total = data.count
+    } else {
+      // 处理数据为空的情况
+      dataList.value = []
+      total.value = 0
+    }
+  } catch (error) {
+    // 异常处理
+    console.error('获取产品列表失败:', error)
+    // 可以在这里添加错误提示或其他处理逻辑
+  } finally {
+    loading.value = false
+    // 确保无论成功与否,都会将加载状态设置为 false
+  }
+}
+const getStroe = () => {
+  getStoreList({
+    page: 1,
+    limit: 100
+  }).then((res) => {
+    console.log(res, '1123123')
+  })
+}
 
+const getTabNumber = async (name) => {
+  const search = tabsConfig[name].search
+  try {
+    const { data } = await getProduct(search)
+    // 处理边界条件
+    if (data && data.list && data.count !== undefined) {
+      tabsConfig[name].total = data.count
+    } else {
+      // 处理数据为空的情况
+      tabsConfig[name].total = 0
+    }
+  } catch (error) {
+    // 异常处理
+    console.error('获取产品列表失败:', error)
+    // 可以在这里添加错误提示或其他处理逻辑
+  } finally {
+    loading.value = false
+    // 确保无论成功与否,都会将加载状态设置为 false
+  }
+}
 const searchSchema = reactive<FormSchema[]>([
   {
-    field: 'nickname',
+    field: 'name',
+    label: '商品名称',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请填写商品名称'
+    }
+  },
+  {
+    field: 'store_id',
+    label: '门店',
+    component: 'Select',
+    componentProps: {
+      placeholder: '请选择要搜索的门店'
+    },
+    optionApi: async () => {
+      const { data } = await getStoreList({ page: 1, limit: 100 })
+      return data.list.map((item) => {
+        return {
+          value: item.id,
+          label: item.name
+        }
+      })
+    }
+  },
+  {
+    field: 'is_verify',
+    label: '审核状态',
+    hidden: true,
+    component: 'Select',
+    value: 'all',
+    componentProps: {
+      placeholder: '全部',
+      options: [
+        {
+          value: 'all',
+          label: '全部'
+        },
+        {
+          value: '-2',
+          label: '强制下架'
+        },
+        {
+          value: '-1',
+          label: '未通过'
+        },
+        {
+          value: '0',
+          label: '待审核'
+        },
+        {
+          value: '1',
+          label: '通过'
+        }
+      ]
+    }
+  },
+  {
+    field: 'is_show',
+    label: '上下架',
+    hidden: true,
+
+    component: 'Select',
+    value: 'all',
+    componentProps: {
+      placeholder: '全部',
+      options: [
+        {
+          value: 'all',
+          label: '全部'
+        },
+        {
+          value: '1',
+          label: '上架'
+        },
+        {
+          value: '0',
+          label: '未上架'
+        }
+      ]
+    }
+  },
+  {
+    field: 'is_del',
+    label: '是否删除',
+    hidden: true,
+    component: 'Select',
+    value: 'all',
+    componentProps: {
+      placeholder: '全部',
+      options: [
+        {
+          value: 'all',
+          label: '全部'
+        },
+        {
+          value: '0',
+          label: '未删除'
+        },
+        {
+          value: '1',
+          label: '已删除'
+        }
+      ]
+    }
+  },
+  {
+    field: 'cate_ids',
     label: '商品分类',
     component: 'Cascader',
     componentProps: {
@@ -78,25 +278,16 @@ const searchSchema = reactive<FormSchema[]>([
         }
       }
     }
-  },
-  searchTime
+  }
 ])
 
-const searchParams = ref<userSearch>({
-  nickname: '',
-  status: '',
-  is_promoter: '',
-  user_type: '',
-  country: '',
-  province: '',
-  city: '',
-  sex: '',
-  user_time_type: 'all',
-  user_time: '',
-  level: '',
-  group_id: '',
-  now_money: '',
-  field_key: 'all'
+const searchParams = ref<goodsSearch>({
+  name: '',
+  cate_ids: '',
+  store_id: '',
+  is_show: '1',
+  is_del: '0',
+  is_verify: '1'
 })
 const setSearchParams = (data: any) => {
   searchParams.value = data
@@ -106,13 +297,29 @@ const setSearchParams = (data: any) => {
 const action = async (type: string, id?: number) => {
   push(`/goods/edit/${type}/${id || 0}`)
 }
+const delPr = async (id: number, is_del: 0 | 1) => {
+  let title = '移除'
+  if (is_del == 0) {
+    title = '恢复'
+  }
+  ElMessageBox.confirm(`是否${title}商品?`, {
+    confirmButtonText: `${title}`,
+    cancelButtonText: '取消',
+    type: 'warning'
+  })
+    .then(async () => {
+      await delProduct(id, is_del)
+      getList()
+
+      ElMessage({
+        showClose: true,
+        message: `${title}成功`,
+        type: 'success'
+      })
+    })
+    .catch(() => {})
+}
 
-// const drawerUser = ref(false)
-// const drawerUserId = ref()
-// const detail = (item) => {
-//   drawerUser.value = true
-//   drawerUserId.value = item.uid
-// }
 const selectChange = (res) => {
   selectList.value = res
 }
@@ -130,14 +337,101 @@ watch(currentPage, (val) => {
 })
 const changeStatus = async (val: any, id: number) => {
   console.log(val, id, 'val')
+  try {
+    const res = await postProductStatus(id, val)
+    if (res) {
+      ElMessage.success('设置成功')
+    }
+  } catch (error) {
+    console.log(error)
+  } finally {
+    getList()
+  }
+  console.log()
 }
-const delData = (res) => {
-  console.log(res)
+const activeName = ref('up')
+
+const tabsClick = async (res) => {
+  if (!res || !res.paneName) {
+    console.error('Invalid res or paneName')
+    return
+  }
+  console.log(res.paneName, activeName.value)
+  if (res.paneName === activeName.value) {
+    return
+  }
+  try {
+    // 设置 schema 配置
+    const schemaConfig = res.paneName === 'all' ? false : true
+    setSchema([
+      { field: 'is_verify', path: 'hidden', value: schemaConfig },
+      { field: 'is_del', path: 'hidden', value: schemaConfig },
+      { field: 'is_show', path: 'hidden', value: schemaConfig }
+    ])
+
+    if (tabsConfig[res.paneName].search) {
+      await setValues(tabsConfig[res.paneName].search)
+    } else {
+      console.warn(`Unknown paneName: ${res.paneName}`)
+    }
+    const data = await getFormData()
+    setSearchParams(data)
+  } catch (error) {
+    console.error('Error in handleClick:', error)
+  }
+}
+const auditMessageBox = (id: number) => {
+  ElMessageBox.confirm('请选择审核结果', '审核', {
+    distinguishCancelAndClose: true,
+    confirmButtonText: '通过',
+    cancelButtonText: '未通过'
+  })
+    .then(() => {
+      audit(id, 1, '')
+    })
+    .catch((action: Action) => {
+      if (action == 'cancel') {
+        ElMessageBox.prompt('请输入拒绝理由', '提示', {
+          inputPlaceholder: '输入正确的拒绝理由以便修改'
+        }).then((res) => {
+          audit(id, -1, res.value)
+          console.log(res)
+        })
+      }
+    })
+}
+
+const audit = async (id, is_verify, refusal) => {
+  try {
+    const res = await postProductAudit({
+      id,
+      refusal,
+      is_verify
+    })
+    if (res && res.status == 200) {
+      let title = '审核通过'
+      if (is_verify == -1) {
+        title = '已拒绝'
+      }
+      ElMessage.success(title)
+      getList()
+    }
+  } catch (error) {
+    console.log(error)
+  }
 }
 </script>
 
 <template>
   <ContentWrap>
+    <ElTabs v-model="activeName" @tab-click="tabsClick">
+      <ElTabPane
+        v-for="(item, index) in tabsConfig"
+        :key="index"
+        :label="`${item.title}${item.total > 0 ? '(' + item.total + ')' : ''}`"
+        :name="index"
+      />
+    </ElTabs>
     <div class="searchBox">
       <Search
         :schema="searchSchema"
@@ -151,7 +445,7 @@ const delData = (res) => {
       <BaseButton type="primary" @click="action('add')">{{ t('exampleDemo.add') }}</BaseButton>
       <BaseButton @click="allSet" :disabled="selectList.length == 0">批量设置</BaseButton>
     </div>
-    <ElTable @selection-change="selectChange" :loading="loading" node-key="id" :data="dataList">
+    <ElTable @selection-change="selectChange" v-loading="loading" node-key="id" :data="dataList">
       <ElTableColumn type="selection" prop="selection" />
       <ElTableColumn prop="id" headerAlign="center" align="center" label="ID" width="70" />
       <ElTableColumn prop="avatar" headerAlign="center" align="center" label="商品图片" width="80">
@@ -160,6 +454,7 @@ const delData = (res) => {
         </template>
       </ElTableColumn>
       <ElTableColumn prop="name" label="商品名称" minWidth="180" />
+      <ElTableColumn prop="store_name" label="门店" minWidth="150" />
       <ElTableColumn prop="price" label="商品售价" minWidth="80" />
       <ElTableColumn prop="stock" label="库存" minWidth="80" />
       <ElTableColumn prop="sales" label="销量" minWidth="80" />
@@ -176,6 +471,19 @@ const delData = (res) => {
           />
         </template>
       </ElTableColumn>
+      <ElTableColumn v-if="activeName == 'all'" prop="is_verify" label="审核状态" minWidth="80">
+        <template #default="{ row }">
+          <ElText v-if="row.is_verify == 1" type="success">已通过</ElText>
+          <ElText v-if="row.is_verify == 0" type="primary">待审核</ElText>
+          <ElText v-if="row.is_verify == -1" type="danger">已拒绝</ElText>
+        </template>
+      </ElTableColumn>
+      <ElTableColumn v-if="activeName == 'all'" prop="price" label="删除状态" minWidth="80">
+        <template #default="{ row }">
+          <ElText v-if="row.is_del == 1" type="danger">已删除</ElText>
+          <ElText v-if="row.is_del == 0" type="primary">未删除</ElText>
+        </template>
+      </ElTableColumn>
       <ElTableColumn
         prop="action"
         label="操作"
@@ -189,15 +497,29 @@ const delData = (res) => {
           <BaseButton link size="small" type="primary" @click="action('edit', row.id)">
             编辑
           </BaseButton>
-          <BaseButton link size="small" type="primary" @click="delData(row)" />
           <ElDivider direction="vertical" />
+          <BaseButton
+            v-if="activeName == 'examine'"
+            link
+            size="small"
+            type="primary"
+            @click="auditMessageBox(row.id)"
+          >
+            审核
+          </BaseButton>
+          <ElDivider v-if="activeName == 'examine'" direction="vertical" />
           <ElDropdown>
             <BaseButton link size="small" type="info">
               <Icon icon="vi-ep:more-filled" />
             </BaseButton>
             <template #dropdown
               ><ElDropdownMenu>
-                <ElDropdownItem>移到回收站 </ElDropdownItem>
+                <ElDropdownItem v-if="activeName != 'trash'" @click="delPr(row.id, 1)"
+                  >移到回收站
+                </ElDropdownItem>
+                <ElDropdownItem v-if="activeName == 'trash'" @click="delPr(row.id, 0)"
+                  >恢复商品
+                </ElDropdownItem>
               </ElDropdownMenu>
             </template>
           </ElDropdown>

+ 5 - 5
vite.config.ts

@@ -154,11 +154,11 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
           changeOrigin: true
           // rewrite: (path) => path.replace(/^\//, '')
         },
-        // '/uploads': {
-        //   target: 'https://zhuangxiu.qiniu1314.com',
-        //   changeOrigin: true
-        //   // rewrite: (path) => path.replace(/^\//, '')
-        // },
+        '/uploads': {
+          target: 'https://zhuangxiu.qiniu1314.com',
+          changeOrigin: true
+          // rewrite: (path) => path.replace(/^\//, '')
+        },
         '/qqmap': {
           target: 'http://apis.map.qq.com',
           changeOrigin: true,